React Native Skia
repository·main·Indexed 27 days ago
https://github.com/shopify/react-native-skiaA high-performance 2D graphics library for React Native that leverages the Skia graphics engine to enable advanced graphics capabilities similar to Chrome, Android, and Flutter. It supports complex animations, animated GIF and WebP formats via Reanimated, and provides an experimental Graphite backend.
What's inside react-native-skia
- React Native Skia is a high-performance 2D graphics library for React Native that leverages the Skia graphics engine. It allows developers to build complex, high-performance graphics and animations within React Native applications.
Expected bundle size increase for React Native Skia
mainWhen adding React Native Skia to your project, you can expect the following increases in download size:
- Android: ~4 MB (when using App Bundles for arm64-bit devices).
- Apple (iOS): ~6 MB.
- Web: ~2.9 MB (gzipped size served through a CDN).
Note that the NPM package download size is larger than these figures because it contains Skia binaries for all target platforms for both iOS and Android.
Use the Picture API for variable drawing commands
mainWhile React Native Skia typically works in retained mode, you should use the
PictureAPI when you need to execute a variable number of drawing commands each frame (immediate mode).A
Picture(typeSkPicture) contains an immutable list of drawing operations that can be reused multiple times on any canvas.To create a picture, use
Skia.PictureRecorder()to begin recording to a canvas, perform your drawing operations, and then callrecorder.finishRecordingAsPicture().import React, { useEffect } from "react"; import { Canvas, Picture, Skia } from "@shopify/react-native-skia"; import { useDerivedValue, useSharedValue, withRepeat, withTiming, } from "react-native-reanimated"; const size = 256; const n = 20; const paint = Skia.Paint(); const recorder = Skia.PictureRecorder(); export const HelloWorld = () => { const progress = useSharedValue(0); useEffect(() => { progress.value = withRepeat(withTiming(1, { duration: 3000 }), -1, true); }, [progress]); const picture = useDerivedValue(() => { "worklet"; const canvas = recorder.beginRecording(Skia.XYWHRect(0, 0, size, size)); const numberOfCircles = Math.floor(progress.value * n); for (let i = 0; i < numberOfCircles; i++) { const alpha = ((i + 1) / n) * 255; const r = ((i + 1) / n) * (size / 2); paint.setColor(Skia.Color(`rgba(0, 122, 255, ${alpha / 255})`)); canvas.drawCircle(size / 2, size / 2, r, paint); } return recorder.finishRecordingAsPicture(); }); return ( <Canvas style={{ flex: 1 }}> <Picture picture={picture} /> </Canvas> ); };Run React Native Skia headlessly on Node.js
mainReact Native Skia can run on Node.js using its offscreen capabilities, allowing you to use the Skia API to draw, encode, and save images.
When running in Node, you must use the CommonJS build and import from specific entry points to avoid dependencies on pure React Native APIs:
- Use
@shopify/react-native-skia/lib/commonjs/web/LoadSkiaWebto load the environment. - Use
@shopify/react-native-skia/lib/commonjs/headlessfor the headless Skia APIs.
After calling
LoadSkiaWeb(), you can access the Skia APIs viagetSkiaExports()or by usingrequire("@shopify/react-native-skia").import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/commonjs/web/LoadSkiaWeb"; import { Circle, drawOffscreen, getSkiaExports, Group, makeOffscreenSurface } from "@shopify/react-native-skia/lib/commonjs/headless"; (async () => { const width = 256; const height = 256; const size = 60; const r = size * 0.33; await LoadSkiaWeb(); // Access Skia via getSkiaExports() const {Skia} = getSkiaExports(); using surface = makeOffscreenSurface(width, height); using image = await drawOffscreen(surface, <Group blendMode="multiply"> <Circle cx={r} cy={r} r={r} color="cyan" /> <Circle cx={size - r} cy={r} r={r} color="magenta" /> <Circle cx={size/2} cy={size - r} r={r} color="yellow" /> </Group>); console.log(image.encodeToBase64()); })();- Use
Enable High Bit Depth for smoother gradients
mainBy default, the canvas renders into an 8-bit surface. To prevent color banding in subtle gradients (especially on OLED displays), use the
highBitDepthprop. This renders into a 16-bit float surface on iOS and a 10-bit surface on Android.Note for Android:
highBitDepthrequires the Graphite backend. If using the default OpenGL backend, it will fall back to 8-bit.import {Canvas, Fill, LinearGradient, vec} from "@shopify/react-native-skia"; const Demo = () => { return ( <Canvas style={{ flex: 1 }} opaque highBitDepth> <Fill> <LinearGradient start={vec(0, 0)} end={vec(0, 512)} colors={["rgb(51, 56, 77)", "rgb(59, 64, 85)"]} /> </Fill> </Canvas> ); };Run development tasks for the Example App
mainUse the following commands to maintain code quality and run tests within the example application directory:Apply effects to a Picture using the layer property
mainThe
Picturecomponent does not follow standard painting rules. To apply effects like blurs to aPicture, wrap it in aGroupand use thelayerproperty to provide aPaintobject containing the desired effect (e.g.,Blur).import React, { useMemo } from "react"; import { Canvas, Skia, Group, Paint, Blur, BlendMode, Picture } from "@shopify/react-native-skia"; export const Demo = () => { const picture = useMemo(() => { const recorder = Skia.PictureRecorder(); const size = 256; const canvas = recorder.beginRecording(Skia.XYWHRect(0, 0, size, size)); const r = 0.33 * size; const paint = Skia.Paint(); paint.setBlendMode(BlendMode.Multiply); paint.setColor(Skia.Color("cyan")); canvas.drawCircle(r, r, r, paint); paint.setColor(Skia.Color("magenta")); canvas.drawCircle(size - r, r, r, paint); paint.setColor(Skia.Color("yellow")); canvas.drawCircle(size / 2, size - r, r, paint); return recorder.finishRecordingAsPicture(); }, []); return ( <Canvas style={{ flex: 1 }}> <Group layer={<Paint><Blur blur={10} /></Paint>}> <Picture picture={picture} /> </Group> </Canvas> ); };Manage Remotion project via CLI commands
mainUse the following npm commands to manage your Remotion project lifecycle:
- Install Dependencies: Run
npm ito install required packages. - Start Preview: Run
npm startto launch the Remotion preview environment. - Render Video: Run
npm run buildto render your video project. - Upgrade Remotion: Run
npm run upgradeto update the Remotion version.
- Install Dependencies: Run
Publish the Android library as a Maven dependency
mainBefore publishing a new version to npm, follow these steps to publish the Android library as a Maven dependency:
- Ensure the Android SDK and NDK are installed.
- Create a
local.propertiesfile in the package folder pointing to your Android SDK and NDK paths. - Delete the existing
mavenfolder. - Execute
./gradlew installArchivesto generate the new artifacts. - Verify that the latest generated files are present in the
mavenfolder with the correct version number.
ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle sdk.dir=/Users/{username}/Library/Android/sdk./gradlew installArchivesScale SVGs using fitbox
mainIf an SVG's root dimensions are in absolute units, the
widthandheightprops onImageSVGwill not scale it. To scale an SVG to fit a specific area (like the canvas size), use thefitboxfunction within aGroupcomponent'stransformprop.Example of scaling an SVG to fit a 256x256 area:
const src = rect(0, 0, svg.width(), svg.height()); const dst = rect(0, 0, 256, 256); <Group transform={fitbox("contain", src, dst)}> <ImageSVG svg={svg} x={0} y={0} width={svg.width()} height={svg.height()} /> </Group>import React from "react"; import { Canvas, ImageSVG, Skia, rect, fitbox, Group } from "@shopify/react-native-skia"; const svg = Skia.SVG.MakeFromString( `<svg viewBox='0 0 20 20' width="20" height="20" xmlns='http://www.w3.org/2000/svg'> <circle cx='10' cy='10' r='10' fill='#00ffff'/> </svg>` )!; const width = 256; const height = 256; const src = rect(0, 0, svg.width(), svg.height()); const dst = rect(0, 0, width, height); export const SVG = () => { return ( <Canvas style={{ flex: 1 }}> <Group transform={fitbox("contain", src, dst)}> <ImageSVG svg={svg} x={0} y={0} width={20} height={20} /> </Group> </Canvas> ); };Apply effects to ImageSVG using layers
mainBecause
ImageSVGuses the Skia SVG module, it doesn't follow standard component painting rules. To apply effects like opacity or blur, wrap theImageSVGin aGroupand use thelayerproperty with aPaintobject.Opacity Example: Use
ColorMatrixwithOpacityMatrixinside aPaintcomponent.Blur Example: Use the
Blurcomponent inside aPaintcomponent.<Group transform={fitbox("contain", src, dst)} layer={<Paint><Blur blur={10} /></Paint>} > <ImageSVG svg={tiger} ... /> </Group>import { Canvas, ImageSVG, Skia, rect, fitbox, useSVG, Group, Paint, Blur } from "@shopify/react-native-skia"; // ... setup src and dst export const SVG = () => { const tiger = useSVG(require("./tiger.svg")); if (!tiger) return null; return ( <Canvas style={{ flex: 1 }}> <Group transform={fitbox("contain", src, dst)} layer={<Paint><Blur blur={10} /></Paint>} > <ImageSVG svg={tiger} x={0} y={0} width={800} height={800} /> </Group> </Canvas> ); };Install dependencies for the documentation website
mainTo install the necessary dependencies for the documentation website, run the following command in theapps/docsdirectory.yarn