React Native Skia

repository·main·Indexed 27 days ago

https://github.com/shopify/react-native-skia

A 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.

Tokens
60.4K
Snippets
159
Records
201
Agent score
91%

What's inside react-native-skia

  1. Expected bundle size increase for React Native Skia

    main

    When 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.

  2. Use the Picture API for variable drawing commands

    main

    While React Native Skia typically works in retained mode, you should use the Picture API when you need to execute a variable number of drawing commands each frame (immediate mode).

    A Picture (type SkPicture) 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 call recorder.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>
      );
    };
  3. Run React Native Skia headlessly on Node.js

    main

    React 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:

    1. Use @shopify/react-native-skia/lib/commonjs/web/LoadSkiaWeb to load the environment.
    2. Use @shopify/react-native-skia/lib/commonjs/headless for the headless Skia APIs.

    After calling LoadSkiaWeb(), you can access the Skia APIs via getSkiaExports() or by using require("@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());
    })();
  4. Enable High Bit Depth for smoother gradients

    main

    By default, the canvas renders into an 8-bit surface. To prevent color banding in subtle gradients (especially on OLED displays), use the highBitDepth prop. This renders into a 16-bit float surface on iOS and a 10-bit surface on Android.

    Note for Android: highBitDepth requires 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>
      );
    };
  5. Apply effects to a Picture using the layer property

    main

    The Picture component does not follow standard painting rules. To apply effects like blurs to a Picture, wrap it in a Group and use the layer property to provide a Paint object 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>
      );
    };
  6. Manage Remotion project via CLI commands

    main

    Use the following npm commands to manage your Remotion project lifecycle:

    • Install Dependencies: Run npm i to install required packages.
    • Start Preview: Run npm start to launch the Remotion preview environment.
    • Render Video: Run npm run build to render your video project.
    • Upgrade Remotion: Run npm run upgrade to update the Remotion version.
  7. Publish the Android library as a Maven dependency

    main

    Before publishing a new version to npm, follow these steps to publish the Android library as a Maven dependency:

    1. Ensure the Android SDK and NDK are installed.
    2. Create a local.properties file in the package folder pointing to your Android SDK and NDK paths.
    3. Delete the existing maven folder.
    4. Execute ./gradlew installArchives to generate the new artifacts.
    5. Verify that the latest generated files are present in the maven folder with the correct version number.
    ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle
    sdk.dir=/Users/{username}/Library/Android/sdk
    ./gradlew installArchives
  8. Scale SVGs using fitbox

    main

    If an SVG's root dimensions are in absolute units, the width and height props on ImageSVG will not scale it. To scale an SVG to fit a specific area (like the canvas size), use the fitbox function within a Group component's transform prop.

    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>
      );
    };
  9. Apply effects to ImageSVG using layers

    main

    Because ImageSVG uses the Skia SVG module, it doesn't follow standard component painting rules. To apply effects like opacity or blur, wrap the ImageSVG in a Group and use the layer property with a Paint object.

    Opacity Example: Use ColorMatrix with OpacityMatrix inside a Paint component.

    Blur Example: Use the Blur component inside a Paint component.

    <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>
      );
    };