尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Expo 仓库内 react-native-view-shot 深度解析:从 ViewShot 组件到 RAW/zip-base64 高性能视图截图

Expo 仓库内 react-native-view-shot 深度解析:从 ViewShot 组件到 RAW/zip-base64 高性能视图截图 Expo 仓库内 react-native-view-shot 深度解析从 ViewShot 组件到 RAW/zip-base64 高性能视图截图【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo本篇基于 Expo 仓库中随 Expo Go 一起维护的react-native-view-shot模块README展开系统讲解该库「把 React Native 视图栅格化为图片」的完整 API 体系——ViewShot组件、captureRef命令式 API、captureScreen全屏截图以及rawzip-base64高性能截图链路并结合 Android/iOS/Web 三端源码印证各参数的真实行为帮助你在 Expo 项目中可靠、高性能地完成视图截图、导出与分享。一、模块定位与安装react-native-view-shot的核心能力只有一句话Capture a React Native view to an image把一个 React Native 视图捕获成图片。在 Expo 仓库中它以源码模块的形式内嵌在 Expo Go 应用的modules目录下其 package.json 显示版本为4.0.3main入口为src/index.js并声明了codegenConfigAndroid Java 包名fr.greweb.reactnativeviewshot说明该模块在新架构下通过 Codegen 生成 TurboModule 桩代码。在独立项目中安装方式如下README 原文yarn add react-native-view-shot # In Expo expo install react-native-view-shot安装后的原生链接要点README 原文要求在 Xcode 中确认react-native-view-shot已正确 link必要时手动安装0.60.x 之前需要执行react-native link react-native-view-shot0.60.x 之后依赖 autolinkiOS 端需要执行npx pod-install安装 CocoaPods 依赖。从仓库结构看模块包含完整的三端实现平台实现文件JS 主入口src/index.jsJS 原生桥接src/RNViewShot.js、src/specs/NativeRNViewShot.tsWeb 端src/RNViewShot.web.js基于html2canvas见 package.json 依赖AndroidRNViewShotModule.java、ViewShot.javaiOSRNViewShot.mm类型定义src/index.d.ts此外Expo 仓库在根目录维护了一个针对性补丁 patches/react-native-view-shot.patch把 iOS 端snapshotContentContainer的类型检查从RCTScrollView放宽为UIScrollView并直接把该UIView作为绘制目标——这解释了为什么新版文档中该选项在 iOS 上对任意UIScrollView实例生效而不局限于 RN 的ScrollView包装类。二、高层 APIViewShot组件README 给出的四种典型用法挂载后手动触发、挂载即捕获、等待图片加载完成、捕获 ScrollView 内容。import ViewShot from react-native-view-shot; function ExampleCaptureOnMountManually { const ref useRef(); useEffect(() { // on mount ref.current.capture().then(uri { console.log(do something with , uri); }); }, []); return ( ViewShot ref{ref} options{{ fileName: Your-File-Name, format: jpg, quality: 0.9 }} Text...Something to rasterize.../Text /ViewShot ); } // alternative function ExampleCaptureOnMountSimpler { const ref useRef(); const onCapture useCallback(uri { console.log(do something with , uri); }, []); return ( ViewShot onCapture{onCapture} captureModemount Text...Something to rasterize.../Text /ViewShot ); } // waiting an image function ExampleWaitingCapture { const ref useRef(); const onImageLoad useCallback(() { ref.current.capture().then(uri { console.log(do something with , uri); }) }, []); return ( ViewShot ref{ref} Text...Something to rasterize.../Text Image ... onLoad{onImageLoad} / /ViewShot ); } // capture ScrollView content // NB: you may need to go the imperative way to use snapshotContentContainer with the scrollview ref instead function ExampleCaptureOnMountSimpler { const ref useRef(); const onCapture useCallback(uri { console.log(do something with , uri); }, []); return ( ScrollView ViewShot onCapture{onCapture} captureModemount Text...The Scroll View Content Goes Here.../Text /ViewShot /ScrollView ); }PropsREADME 原文children要栅格化的实际内容options与captureRef方法相同的 optionscaptureModestring不定义默认不自动截图需用 ref 自己调用capture()mount挂载时捕获一次。注意它不会等待图片加载——若内容有Image应使用默认模式无 captureMode在Image#onLoad后手动调用viewShotRef.capture()continuousEXPERIMENTAL持续不断地大量截图面向极特殊场景updateEXPERIMENTAL每次 React 重绘on did update时截图面向极特殊场景onCapture定义了captureMode时捕获成功回调参数为截图结果 URIonCaptureFailure定义了captureMode时捕获失败回调。源码印证组件内部到底做了什么ViewShot 类实现揭示了几个 README 未展开的关键机制首次布局等待。组件内部维护firstLayoutPromisecapture()会先等第一次onLayout事件再执行捕获L212-L218。这正是 FAQ 中「可以用ViewShot组件自动等待首次onLayout避免The content size must not be zero报错」的原理。临时文件自动释放。每次捕获成功后onCapture会把上一次捕获的 URI 用 500ms 延迟调用releaseCapture释放L230-L239这就是 README 所说的「ViewShot组件在你多次捕获时会用它对 continuous capture 防止文件泄漏」。collapsable{false}自动设置。render()返回的View固定带collapsable{false}L295-L307直接规避了 FAQ 中 Android 端Trying to resolve view with tag {tagID} which doesnt exist的坑。captureMode的防呆检查。开发模式下checkCompatiblePropsL178-L196会警告两类误用定义了captureMode却漏传onCapturecontinuous/update模式下options.result不是tmpfile持续截图用 tmpfile 可配合释放机制避免 base64 大字符串在 bridge 上堆积。continuous 模式的节流逻辑。syncCaptureLoop用requestAnimationFrame循环且只有当上一次捕获结果已经返回lastCapturedURI变化才发起下一次捕获L247-L259天然形成「上一帧未结束则不抢跑」的背压控制。另外capture()在组件卸载后会返回一个永不 resolve 的neverEndingPromiseL8、L216即「组件卸载后你永远不会收到回调」避免悬空 Promise。三、captureRef(view, options)低层命令式 APIimport { captureRef } from react-native-view-shot; captureRef(viewRef, { format: jpg, quality: 0.8, }).then( (uri) console.log(Image saved to, uri), (error) console.error(Oops, snapshot failed, error) );返回图片 URI 的 Promise。view是 React Native 组件的 refoptions完整清单README 原文 index.d.ts 类型补充选项类型默认值说明fileNamestring-仅 Android输出文件名至少 3 个字符width/heightnumber-最终图片尺寸从 View 边界缩放想要原始像素尺寸就不要传formatstringpngpng/jpg/webmAndroid/rawAndroidARGB 像素数组qualitynumber10.0–1.0仅对有损格式jpg有效resultstringtmpfiletmpfile默认临时文件仅应用运行期间存在、base64裸 base64 字符串仅小图避免 bridge 卡顿注意不是 data uri、data-uribase64 加 Data URI scheme 头、zip-base64Androidzip/deflate 压缩后再 base64snapshotContentContainerboolfalse为 true 且 view 是 ScrollView 时按 content container 高度而非容器高度计算handleGLSurfaceViewOnAndroidboolfalseAndroid 上捕获 SurfaceView/GL 视图默认 false 因性能影响显著useRenderInContextboolfalse仅 iOS改用renderInContext代替drawViewHierarchyInRect部分场景更可用JS 层 validateOptions 会对 options 做校验与强制纠正非法width/height非正数被删除、quality越界回退为 1、非法format/result回退默认值并在__DEV__下以console.warn逐条提示。平台差异在这里硬编码webm、raw格式与zip-base64结果只在 Android 被接受L26-L32。原生桥接与三端调用链JS 侧 captureRef 先做 ref 解包支持{ current }对象或组件实例再用findNodeHandle解析出数字 tag然后调用 TurboModuleRNViewShot.captureRef(tag, options)。桥接规格定义在 NativeRNViewShot.tscaptureRef、captureScreen返回PromisestringreleaseCapture同步返回。Android 侧RNViewShotModule.javacaptureRef通过 Fabric 的FabricUIManager.addUIBlock把实际绘制投递到 UI 线程L102-L103ViewShot类实现UIBlock接口tmpfile的临时文件由 createTempFile 创建在内部/外部 cache 目录中选剩余空间更大的一方落盘默认前缀ReactNative-snapshot-image传入fileName时则以其为前缀模块invalidate()时触发CleanTask清理两个 cache 目录中所有该前缀的残留文件L122-L160对应 README「tmpfile 截图在应用关闭后自动清理」releaseCapture只删除位于 cache 目录内的文件L54-L64是一种路径安全约束。iOS 侧RNViewShot.mm通过uiManager addUIBlock拿到viewRegistry解析 tagL53-L68尺寸小于 0.1 时回退到view.bounds.size或 ScrollView 的contentSize仍为 0/负数则拒绝并报错The content size must not be zero or negativeL94-L100——这正是 FAQ 中该报错的来源useRenderInContext二选一renderInContext无法捕获渐变或完整 ScrollView 内容但适合大视图drawViewHierarchyInRect在大视图上会静默失败并产出空白图L114-L122 的注释写得很直白图片编码在后台队列执行jpg 用UIImageJPEGRepresentation(image, quality)其余走 PNGbase64/data-uri/tmpfile经RCTTempFilePath落到临时目录.../ReactNative/三条输出路径L144-L185releaseCapture仅删除临时目录ReactNative子路径下的文件L36-L46。Web 侧RNViewShot.web.js基于html2canvas渲染 DOMtmpfile未实现、会告警并降级返回>import { releaseCapture } from react-native-view-shot; releaseCapture(uri);释放之前捕获的 URI对tmpfile结果是真正删文件对其他result类型是 no-op。README 提醒tmpfile 截图在应用关闭后会自动清理Android 的CleanTask与createTempFile前缀过滤、iOS 的临时目录机制印证了这一点一般场景不必手动处理但continuous这类高频捕获场景下ViewShot组件已内置「捕获成功后延迟 500ms 释放上一个 URI」的防泄漏逻辑。五、captureScreen()Android 与 iOS 专属import { captureScreen } from react-native-view-shot; captureScreen({ format: jpg, quality: 0.8, }).then( (uri) console.log(Image saved to, uri), (error) console.error(Oops, snapshot failed, error) );该方法以原生硬件级截屏方式捕获当前屏幕显示内容不需要 ref也不作用于视图层——因此 ScrollView 只截到当前可见部分无法整屏展开。options 与captureRef相同。源码上这是一个「tag -1 的 captureRef」Android 端 captureScreen 直接转调captureRef((double) -1, ...)tag 为 -1 时取Activity的android.R.id.content视图ViewShot.java L197-L201iOS 端同理转调captureRef:[-1]并取keyWindowRNViewShot.mm L29-L34。Web 端则是把document.body当普通视图处理。六、平台互操作性表Interoperability Table快照不保证像素级完美且行为随平台不同。以下是 README 列出的差异与规避方法测试机型iPhone 6 / iOSNexus 5 / Android。系统iOSAndroidWindowsView, Text, Image, ..YESYESYESWebViewYESYES1YESgl-react v2YESNO2NO3react-native-videoNONONOreact-native-mapsYESNO4NO3react-native-svgYESYESmaybe?react-native-cameraNOYESNO3需要用View collapsable{false}父级包一层再对该父级截图。返回空图不是 Promise reject。组件本身缺少该平台支持。可改用 react-native-maps 自带的 takeSnapshot 能力。Android 端源码与这张表互相印证TextureView子视图会被主动遍历并通过getBitmap 变换矩阵合成回主 CanvasViewShot.java L380-L398而SurfaceView只有在handleGLSurfaceViewOnAndroid为 true 时才走PixelCopyAPI 24或旧版getDrawingCache路径L399-L427——这解释了为何 GL 类组件在 Android 上「不报错但截到空图」。七、性能优化RAW 格式与 zip-base64README 指出 profiling 发现三大性能因素bitmap 内存反分配、Base64 输出缓冲反分配、PNG/JPG 压缩。对应引入的四类优化在 ViewShot.java 中均有实体实现可复用 Bitmap 池getBitmapForScreenshot在一个WeakHashMap支撑的集合里按宽高精确匹配复用 Bitmap找不到才createBitmapL499-L563可复用输出缓冲静态outputBufferReusableByteArrayOutputStream预分配 64KBPREALLOCATE_SIZEasBuffer(size)直接ByteBuffer.wrap内部数组避免内存拷贝L566-L627RAW 格式直接copyPixelsToBuffer写出 ARGB 数组完全绕过压缩L438-L442ZIP deflate 压缩zip-base64用Deflater先压缩再 base64比Bitmap.compress快。RAW Imagesformat: raw对应一个 ARGB 像素数组优势是不压缩、极快README 给出的实测口径截图本身小于 16ms。RAW 支持zip-base64、base64、tmpfile三种 result。RAW 磁盘文件内容格式为${width}:${height}|${base64}Android 端 saveToRawFileOnDevice 写出的resolution头正是%d:%d|zip-base64/base64路径中仅 RAW 会拼接该头见 L271-L303。zip-base64 与 RAW 的配合用法README 原文示例const fs require(fs); const zlib require(zlib); const PNG require(pngjs).PNG; const Buffer require(buffer).Buffer; const format Platform.OS android ? raw : png; const result Platform.OS android ? zip-base64 : base64; captureRef(this.ref, { result, format }).then((data) { // expected pattern width:height|, example: 1080:1731| const resolution /^(\d):(\d)\|/g.exec(data); const width (resolution || [, 0, 0])[1]; const height (resolution || [, 0, 0])[2]; const base64 data.substr((resolution || [])[0].length || 0); // convert from base64 to Buffer const buffer Buffer.from(base64, base64); // un-compress data const inflated zlib.inflateSync(buffer); // compose PNG const png new PNG({ width, height }); png.data inflated; const pngData PNG.sync.write(png); // save composed PNG fs.writeFileSync(output, pngData); });注意zlib.inflate打包 PNG 是 CPU 密集型操作README 建议用process.fork()子进程方式做 raw → PNG 的转换示例服务端代码还需yarn add pngjs。README 备注该代码已在大型商业项目中验证此处按原文保留其自述不作额外引申。八、Troubleshooting / FAQREADME 原文全量整理1. 想保存到文件存到相机相册使用 react-native-cameraroll存到任意文件路径使用 react-native-fs 之类工具更复杂需求可自写原生模块。2. 快照 Promise 被 rejectVideo / GL 等特殊组件不保证可截。失败时captureRef的 Promise 会 reject库本身不会崩溃。对应源码中统一的错误码E_UNABLE_TO_SNAPSHOTViewShot.java L68。3. 简单视图却得到黑图/空白/报错对照上文互操作表含不支持组件的 View整体快照都可能被污染。4. 黑色背景代替透明 / 文字周围出现奇怪边框优先给被截的 view 设置背景色避免透明像素带来的怪异边缘。5. Android 报Trying to resolve view with tag {tagID} which doesnt exist要截取的View 必须collapsable{false}某些内容甚至需要包一层View collapsable{false}才可截图否则该 view 不对应任何原生 UI View。直接改用ViewShot组件即可——其render()固定设置了collapsable{false}src/index.js L300。6. 报The content size must not be zero or negative.不要「即时」截图至少等第一次onLayout或加超时否则 View 尚未就绪有Image时等其onLoad也安全。ViewShot组件会自动等待首次onLayout。该报错文本可在 iOS 端 RNViewShot.mm L98 找到出处Android 端对w 0 || h 0抛Impossible to snapshot the view: view is invalidViewShot.java L348-L350。7. 截图尺寸是宽高选项的 2~3 倍快照结果以真实像素为尺寸而 RN style 中的 width/height 单位是 point。可在 options 中显式传width/height强制缩放Android 端用Bitmap.createScaledBitmap实现ViewShot.java L430-L435可能影响清晰度。8. Android 捕获 GL 视图需开启handleGLSurfaceViewOnAndroid/** * if true and when view is a SurfaceView or have it in the view tree, view will be captured. * False by default, because it can have signoficant performance impact */ handleGLSurfaceViewOnAndroid?: boolean;9. 用expo-sharing分享截图结果tmpfile默认 result最适配调用shareAsync前记得给结果补上file://前缀captureRef(viewRef) .then((uri) Sharing.shareAsync(file://${uri}, options))九、小结在 Expo 项目中的选型建议一次性、可控时机截图优先ViewShot组件 手动capture()等待首次布局、自动释放旧 tmpfile、自动collapsable{false}三个坑全部规避整页长图snapshotContentContainer: trueiOS 依赖 Expo 补丁放宽到UIScrollView或命令式地对 ScrollView ref 调captureRef需要分享/上传文件默认tmpfile配合file://前缀交给expo-sharing需要字符串直传Web/桥传场景小图用data-uri/base64Android 大图高吞吐场景用rawzip-base64在 Node 侧解压重组 PNG全屏截图captureScreen()仅 Android/iOS 原生语义Web 退化为document.body渲染。以上能力与限制均可在当前仓库的 模块源码目录、补丁文件 与 README 中逐条对照验证。【免费下载链接】expoAn open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.项目地址: https://gitcode.com/GitHub_Trending/ex/expo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表