{"id":13541486,"url":"https://github.com/gre/react-native-view-shot","last_synced_at":"2025-10-04T12:44:15.796Z","repository":{"id":37502889,"uuid":"66399014","full_name":"gre/react-native-view-shot","owner":"gre","description":"Snapshot a React Native view and save it to an image","archived":false,"fork":false,"pushed_at":"2024-12-06T21:21:28.000Z","size":7648,"stargazers_count":2801,"open_issues_count":121,"forks_count":354,"subscribers_count":15,"default_branch":"master","last_synced_at":"2025-05-07T21:57:56.537Z","etag":null,"topics":["capture","react-native","snapshot"],"latest_commit_sha":null,"homepage":"https://github.com/gre/react-native-view-shot-example","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gre.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-08-23T20:02:58.000Z","updated_at":"2025-05-05T07:07:21.000Z","dependencies_parsed_at":"2024-08-26T04:05:58.801Z","dependency_job_id":"29dbe262-7283-45d3-8483-373fee37e358","html_url":"https://github.com/gre/react-native-view-shot","commit_stats":{"total_commits":289,"total_committers":82,"mean_commits":3.524390243902439,"dds":0.532871972318339,"last_synced_commit":"71b3a2b925d293beb2928fc79a5e85667290966c"},"previous_names":[],"tags_count":50,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gre%2Freact-native-view-shot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gre%2Freact-native-view-shot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gre%2Freact-native-view-shot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gre%2Freact-native-view-shot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gre","download_url":"https://codeload.github.com/gre/react-native-view-shot/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254133155,"owners_count":22020287,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["capture","react-native","snapshot"],"created_at":"2024-08-01T10:00:48.907Z","updated_at":"2025-10-04T12:44:10.750Z","avatar_url":"https://github.com/gre.png","language":"JavaScript","readme":"# react-native-view-shot ![](https://img.shields.io/npm/v/react-native-view-shot.svg) ![](https://img.shields.io/badge/react--native-%2040+-05F561.svg)\n\nCapture a React Native view to an image.\n\n\u003cimg src=\"./.readme/recursive.gif\" width=300 /\u003e\n\n## Install\n\n```bash\nyarn add react-native-view-shot\n\n# In Expo\n\nexpo install react-native-view-shot\n```\n\nMake sure `react-native-view-shot` is correctly linked in Xcode (might require a manual installation, refer to [React Native doc](https://reactnative.dev/docs/linking-libraries-ios.html)).\n\n**Before React Native 0.60.x you would have to:**\n\n```bash\nreact-native link react-native-view-shot\n```\n\n**Since 0.60.x, [autolink](https://github.com/react-native-community/cli/blob/master/docs/autolinking.md) should just work**, on iOS, you'll need to ensure the CocoaPods are installed with:\n\n```bash\nnpx pod-install\n```\n\n## High Level API\n\n```js\nimport ViewShot from \"react-native-view-shot\";\n\nfunction ExampleCaptureOnMountManually {\n  const ref = useRef();\n\n  useEffect(() =\u003e {\n    // on mount\n    ref.current.capture().then(uri =\u003e {\n      console.log(\"do something with \", uri);\n    });\n  }, []);\n\n  return (\n    \u003cViewShot ref={ref} options={{ fileName: \"Your-File-Name\", format: \"jpg\", quality: 0.9 }}\u003e\n      \u003cText\u003e...Something to rasterize...\u003c/Text\u003e\n    \u003c/ViewShot\u003e\n  );\n}\n\n// alternative\nfunction ExampleCaptureOnMountSimpler {\n  const ref = useRef();\n\n  const onCapture = useCallback(uri =\u003e {\n    console.log(\"do something with \", uri);\n  }, []);\n\n  return (\n    \u003cViewShot onCapture={onCapture} captureMode=\"mount\"\u003e\n      \u003cText\u003e...Something to rasterize...\u003c/Text\u003e\n    \u003c/ViewShot\u003e\n  );\n}\n\n// waiting an image\n\nfunction ExampleWaitingCapture {\n  const ref = useRef();\n\n  const onImageLoad = useCallback(() =\u003e {\n    ref.current.capture().then(uri =\u003e {\n      console.log(\"do something with \", uri);\n    })\n  }, []);\n\n  return (\n    \u003cViewShot ref={ref}\u003e\n      \u003cText\u003e...Something to rasterize...\u003c/Text\u003e\n      \u003cImage ... onLoad={onImageLoad} /\u003e\n    \u003c/ViewShot\u003e\n  );\n}\n\n// capture ScrollView content\n// NB: you may need to go the \"imperative way\" to use snapshotContentContainer with the scrollview ref instead\nfunction ExampleCaptureOnMountSimpler {\n  const ref = useRef();\n\n  const onCapture = useCallback(uri =\u003e {\n    console.log(\"do something with \", uri);\n  }, []);\n\n  return (\n    \u003cScrollView\u003e\n      \u003cViewShot onCapture={onCapture} captureMode=\"mount\"\u003e\n        \u003cText\u003e...The Scroll View Content Goes Here...\u003c/Text\u003e\n      \u003c/ViewShot\u003e\n    \u003c/ScrollView\u003e\n  );\n}\n```\n\n**Props:**\n\n- **`children`**: the actual content to rasterize.\n- **`options`**: the same options as in `captureRef` method.\n- **`captureMode`** (string):\n  - if not defined (default). the capture is not automatic and you need to use the ref and call `capture()` yourself.\n  - `\"mount\"`. Capture the view once at mount. (It is important to understand image loading won't be waited, in such case you want to use `\"none\"` with `viewShotRef.capture()` after `Image#onLoad`.)\n  - `\"continuous\"` EXPERIMENTAL, this will capture A LOT of images continuously. For very specific use-cases.\n  - `\"update\"` EXPERIMENTAL, this will capture images each time React redraw (on did update). For very specific use-cases.\n- **`onCapture`**: when a `captureMode` is defined, this callback will be called with the capture result.\n- **`onCaptureFailure`**: when a `captureMode` is defined, this callback will be called when a capture fails.\n\n## `captureRef(view, options)` lower level imperative API\n\n```js\nimport { captureRef } from \"react-native-view-shot\";\n\ncaptureRef(viewRef, {\n  format: \"jpg\",\n  quality: 0.8,\n}).then(\n  (uri) =\u003e console.log(\"Image saved to\", uri),\n  (error) =\u003e console.error(\"Oops, snapshot failed\", error)\n);\n```\n\nReturns a Promise of the image URI.\n\n- **`view`** is a reference to a React Native component.\n- **`options`** may include:\n  - **`fileName`** _(string)_: (Android only) the file name of the file. Must be at least 3 characters long.\n  - **`width`** / **`height`** _(number)_: the width and height of the final image (resized from the View bound. don't provide it if you want the original pixel size).\n  - **`format`** _(string)_: either `png` or `jpg` or `webm` (Android). Defaults to `png`.\n  - **`quality`** _(number)_: the quality. 0.0 - 1.0 (default). (only available on lossy formats like jpg)\n  - **`result`** _(string)_, the method you want to use to save the snapshot, one of:\n    - `\"tmpfile\"` (default): save to a temporary file _(that will only exist for as long as the app is running)_.\n    - `\"base64\"`: encode as base64 and returns the raw string. Use only with small images as this may result of lags (the string is sent over the bridge). _N.B. This is not a data uri, use `data-uri` instead_.\n    - `\"data-uri\"`: same as `base64` but also includes the [Data URI scheme](https://en.wikipedia.org/wiki/Data_URI_scheme) header.\n  - **`snapshotContentContainer`** _(bool)_: if true and when view is a ScrollView, the \"content container\" height will be evaluated instead of the container height.\n  - [iOS] **`useRenderInContext`** _(bool)_: change the iOS snapshot strategy to use method `renderInContext` instead of `drawViewHierarchyInRect` which may help for some use cases.\n\n## `releaseCapture(uri)`\n\nThis method release a previously captured `uri`. For tmpfile it will clean them out, for other result types it just won't do anything.\n\nNB: the tmpfile captures are automatically cleaned out after the app closes, so you might not have to worry about this unless advanced usecases. The `ViewShot` component will use it each time you capture more than once (useful for continuous capture to not leak files).\n\n## `captureScreen()` Android and iOS Only\n\n```js\nimport { captureScreen } from \"react-native-view-shot\";\n\ncaptureScreen({\n  format: \"jpg\",\n  quality: 0.8,\n}).then(\n  (uri) =\u003e console.log(\"Image saved to\", uri),\n  (error) =\u003e console.error(\"Oops, snapshot failed\", error)\n);\n```\n\nThis method will capture the contents of the currently displayed screen as a native hardware screenshot. It does not require a ref input, as it does not work at the view level. This means that ScrollViews will not be captured in their entirety - only the portions currently visible to the user.\n\nReturns a Promise of the image URI.\n\n- **`options`**: the same options as in `captureRef` method.\n\n### Advanced Examples\n\n[Checkout react-native-view-shot-example](example)\n\n## Interoperability Table\n\n\u003e Snapshots are not guaranteed to be pixel perfect. It also depends on the platform. Here is some difference we have noticed and how to workaround.\n\nModel tested: iPhone 6 (iOS), Nexus 5 (Android).\n\n| System                | iOS              | Android           | Windows                |\n| --------------------- | ---------------- | ----------------- | ---------------------- |\n| View,Text,Image,..    | YES              | YES               | YES                    |\n| WebView               | YES              | YES\u003csup\u003e1\u003c/sup\u003e   | YES                    |\n| gl-react v2           | YES              | NO\u003csup\u003e2\u003c/sup\u003e    | NO\u003csup\u003e3\u003c/sup\u003e         |\n| react-native-video    | NO               | NO                | NO                     |\n| react-native-maps     | YES              | NO\u003csup\u003e4\u003c/sup\u003e    | NO\u003csup\u003e3\u003c/sup\u003e         |\n| react-native-svg      | YES              | YES               | maybe?                 |\n| react-native-camera   | NO               | YES               | NO \u003csup\u003e3\u003c/sup\u003e        |\n\n\u003e\n\n1. Only supported by wrapping a `\u003cView collapsable={false}\u003e` parent and snapshotting it.\n2. It returns an empty image (not a failure Promise).\n3. Component itself lacks platform support.\n4. But you can just use the react-native-maps snapshot function: https://github.com/airbnb/react-native-maps#take-snapshot-of-map\n\n## Performance Optimization\n\nDuring profiling captured several things that influence on performance:\n\n1. (de-)allocation of memory for bitmap\n2. (de-)allocation of memory for Base64 output buffer\n3. compression of bitmap to different image formats: PNG, JPG\n\nTo solve that in code introduced several new approaches:\n\n- reusable images, that reduce load on GC;\n- reusable arrays/buffers that also reduce load on GC;\n- RAW image format for avoiding expensive compression;\n- ZIP deflate compression for RAW data, that works faster in compare to `Bitmap.compress`\n\nmore details and code snippet are below.\n\n### RAW Images\n\nIntroduced a new image format RAW. it correspond a ARGB array of pixels.\n\nAdvantages:\n\n- no compression, so its supper quick. Screenshot taking is less than 16ms;\n\nRAW format supported for `zip-base64`, `base64` and `tmpfile` result types.\n\nRAW file on disk saved in format: `${width}:${height}|${base64}` string.\n\n### zip-base64\n\nIn compare to BASE64 result string this format fast try to apply zip/deflate compression on screenshot results\nand only after that convert results to base64 string. In combination zip-base64 + raw we got a super fast\napproach for capturing screen views and deliver them to the react side.\n\n### How to work with zip-base64 and RAW format?\n\n```js\nconst fs = require(\"fs\");\nconst zlib = require(\"zlib\");\nconst PNG = require(\"pngjs\").PNG;\nconst Buffer = require(\"buffer\").Buffer;\n\nconst format = Platform.OS === \"android\" ? \"raw\" : \"png\";\nconst result = Platform.OS === \"android\" ? \"zip-base64\" : \"base64\";\n\ncaptureRef(this.ref, { result, format }).then((data) =\u003e {\n  // expected pattern 'width:height|', example: '1080:1731|'\n  const resolution = /^(\\d+):(\\d+)\\|/g.exec(data);\n  const width = (resolution || [\"\", 0, 0])[1];\n  const height = (resolution || [\"\", 0, 0])[2];\n  const base64 = data.substr((resolution || [\"\"])[0].length || 0);\n\n  // convert from base64 to Buffer\n  const buffer = Buffer.from(base64, \"base64\");\n  // un-compress data\n  const inflated = zlib.inflateSync(buffer);\n  // compose PNG\n  const png = new PNG({ width, height });\n  png.data = inflated;\n  const pngData = PNG.sync.write(png);\n  // save composed PNG\n  fs.writeFileSync(output, pngData);\n});\n```\n\nKeep in mind that packaging PNG data is a CPU consuming operation as a `zlib.inflate`.\n\nHint: use `process.fork()` approach for converting raw data into PNGs.\n\n\u003e Note: code is tested in large commercial project.\n\n\u003e Note #2: Don't forget to add packages into your project:\n\u003e\n\u003e ```js\n\u003e yarn add pngjs\n\u003e yarn add zlib\n\u003e ```\n\n## Troubleshooting / FAQ\n\n### Saving to a file?\n\n- If you want to save the snapshotted image result to the CameraRoll, just use https://github.com/react-native-cameraroll/react-native-cameraroll\n- If you want to save it to an arbitrary file path, use something like https://github.com/itinance/react-native-fs\n- For any more advanced needs, you can write your own (or find another) native module that would solve your use-case.\n\n### The snapshot is rejected with an error?\n\n- Support of special components like Video / GL views is not guaranteed to work. In case of failure, the `captureRef` promise gets rejected (the library won't crash).\n\n### get a black or blank result or still have an error with simple views?\n\nCheck the **Interoperability Table** above. Some special components are unfortunately not supported. If you have a View that contains one of an unsupported component, the whole snapshot might be compromised as well.\n\n### black background instead of transparency / weird border appear around texts?\n\n- It's preferable to **use a background color on the view you rasterize** to avoid transparent pixels and potential weirdness that some border appear around texts.\n\n### on Android, getting \"Trying to resolve view with tag '{tagID}' which doesn't exist\"\n\n\u003e you need to make sure `collapsable` is set to `false` if you want to snapshot a **View**. Some content might even need to be wrapped into such `\u003cView collapsable={false}\u003e` to actually make them snapshotable! Otherwise that view won't reflect any UI View. ([found by @gaguirre](https://github.com/gre/react-native-view-shot/issues/7#issuecomment-245302844))\n\nAlternatively, you can use the `ViewShot` component that will have `collapsable={false}` set to solve this problem.\n\n### Getting \"The content size must not be zero or negative.\"\n\n\u003e Make sure you don't snapshot instantly, you need to wait at least there is a first `onLayout` event, or after a timeout, otherwise the View might not be ready yet. (It should also be safe to just wait Image `onLoad` if you have one). If you still have the problem, make sure your view actually have a width and height \u003e 0.\n\nAlternatively, you can use the `ViewShot` component that will wait the first `onLayout`.\n\n### Snapshotted image does not match my width and height but is twice/3-times bigger\n\nThis is because the snapshot image result is in real pixel size where the width/height defined in a React Native style are defined in \"point\" unit. You might want to set width and height option to force a resize. (might affect image quality)\n\n### on Android, capture GL Views\n\nA prop may be necessary to properly capture GL Surface View in the view tree:\n\n```js\n/**\n  * if true and when view is a SurfaceView or have it in the view tree, view will be captured.\n  * False by default, because it can have signoficant performance impact\n  */\nhandleGLSurfaceViewOnAndroid?: boolean;\n```\n\n### Trying to share the capture result with `expo-sharing`?\n\n`tmpfile` or the default capture result works best for this. Just be sure to prepend `file://` to result before you call `shareAsync`.\n\n```js\ncaptureRef(viewRef)\n  .then((uri) =\u003e Sharing.shareAsync(`file://${uri}`, options)\n```\n\n---\n\n## Thanks\n\n- To initial iOS work done by @jsierles in https://github.com/jsierles/react-native-view-snapshot\n- To React Native implementation of takeSnapshot in iOS by @nicklockwood\n- To Windows implementation by @ryanlntn\n","funding_links":[],"categories":["JavaScript","\u003ca name=\"Image-\u0026-Audio-\u0026-Video-\u0026-Docs:-Native-Modules\"\u003eImage, Audio, Video \u0026 Docs: Native Modules\u003c/a\u003e","Components","Snapshot Testing"],"sub_categories":["Image","Browser-based Testing Tools"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgre%2Freact-native-view-shot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgre%2Freact-native-view-shot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgre%2Freact-native-view-shot/lists"}