React Native Skia: Drawing Custom Graphics and Canvas
AI generated
RN
native
React Native · Skia · Canvas
React Native Skia: Drawing Custom Graphics
The same graphics engine as Chrome and Flutter, right inside the app

React Native Skia brings Google's Skia graphics engine, the same one powering Chrome, Android, and Flutter, directly into React Native through JSI. Instead of building shapes out of native views or SVG elements, Skia draws onto a single native surface, which makes custom graphics like signature pads, custom charts, or image filters noticeably faster than classic view-based approaches.

13 min read Skia.Path Canvas rendering Shaders

1. What is Skia, and why React Native Skia?

Skia is Google's open source 2D graphics engine, used for years in Chrome, ChromeOS, Android itself, and, for several versions now, in Flutter as well. It provides a fast, cross-platform drawing API that renders paths, text, images, and shaders onto a bitmap surface, regardless of whether Metal on iOS or OpenGL or Vulkan on Android sits underneath. React Native Skia, built by Shopify, wires exactly this engine into React Native through JSI and exposes it as declarative React components.

The key difference from classic React Native rendering is that Skia does not create a native view per element. A canvas with a hundred drawn circles stays a single native surface with a hundred draw commands, whereas a view-based approach would create a hundred individual native views, each with its own layout and rendering overhead. For custom, often data-driven graphics, that is exactly why Skia stays so much faster in practice.

2. The canvas foundation: Canvas, Group, and Paint

Every Skia drawing starts with a Canvas component that reserves a fixed area of the app as a drawing surface. Inside the canvas, primitives such as Circle, Rect, Path, or Text are placed declaratively as React children, similar to what you know from SVG libraries, except everything ultimately renders onto the same native surface instead of individual views.

Visual properties like color, fill, stroke width, or opacity are controlled through Paint objects or direct props such as color and style. Group lets you combine several primitives and transform them together, or apply a shared shader across them, which is very handy for reusable graphic building blocks.


import { Canvas, Circle, Path, Group } from '@shopify/react-native-skia';

function BasicShapes() {
  return (
    <Canvas style={{ width: 300, height: 300 }}>
      <Group>
        <Circle cx={150} cy={100} r={60} color="#4338ca" />
        <Path
          path="M 40 220 L 120 180 L 200 240 L 260 200"
          color="#22d3ee"
          style="stroke"
          strokeWidth={4}
        />
      </Group>
    </Canvas>
  );
}

3. Drawing paths: Skia.Path for custom shapes

For anything beyond simple circles and rectangles, the imperative Path API is the central building block. Calling Skia.Path.Make() creates an empty path object, which is then built up step by step using methods like moveTo, lineTo, and cubicTo, exactly like the Canvas 2D API in the browser, only with native performance behind it.

That path object can then be passed as a path prop to a Path component and recalculated as often as needed, say on every touch event of a signature pad or on every new data point of a chart. Because path calculation itself is plain JavaScript or worklet code, it combines well with Reanimated, which this article covers later on.

4. Practical example: a signature pad with touch events

A signature pad is a classic use case for Skia: on every touch-move event, a new point gets appended to the current path, usually with quadTo instead of individual lineTo segments so the corners between points stay smooth. The path itself lives in a ref or shared value so it does not need to be recreated through React on every single frame.

Once the signature is done, the result can be exported straight from the canvas as an image via makeImageSnapshot(), for example to send it as a base64 string to a backend. This export happens natively, without a separate screenshot mechanism, which makes signature pads with Skia considerably simpler than writing a dedicated native bridge for it used to be.


import { Canvas, Path, Skia, useCanvasRef } from '@shopify/react-native-skia';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import { useState } from 'react';

function SignaturePad() {
  const canvasRef = useCanvasRef();
  const [path, setPath] = useState(() => Skia.Path.Make());

  const pan = Gesture.Pan()
    .onStart((e) => {
      path.moveTo(e.x, e.y);
      setPath(path.copy());
    })
    .onUpdate((e) => {
      path.lineTo(e.x, e.y);
      setPath(path.copy());
    });

  async function exportSignature() {
    const image = canvasRef.current?.makeImageSnapshot();
    const base64 = image?.encodeToBase64();
    await uploadSignature(base64);
  }

  return (
    <GestureDetector gesture={pan}>
      <Canvas ref={canvasRef} style={{ width: 320, height: 200 }}>
        <Path path={path} color="#111827" style="stroke" strokeWidth={3} />
      </Canvas>
    </GestureDetector>
  );
}

5. Practical example: a performant custom chart

Charts with many data points are where Skia's advantage really shows. Instead of creating an individual view or SVG element per data point, the entire line is calculated as a single path from the data values and handed to the native surface in one draw command. With several hundred points, the result stays smooth, while view-based approaches often noticeably stutter at that scale.

On top of that, the fill area under the line can get a LinearGradient shader without any extra library, since Skia ships shaders as a native concept. That produces charts that look close to native analytics dashboards, without needing image assets or complex SVG definitions to get there.


function LineChart({ points }: { points: number[] }) {
  const path = useMemo(() => {
    const p = Skia.Path.Make();
    points.forEach((value, i) => {
      const x = i * (300 / (points.length - 1));
      const y = 200 - value * 1.5;
      i === 0 ? p.moveTo(x, y) : p.lineTo(x, y);
    });
    return p;
  }, [points]);

  return (
    <Canvas style={{ width: 300, height: 200 }}>
      <Path path={path} style="stroke" strokeWidth={2.5} color="#22d3ee">
        <LinearGradient start={vec(0, 0)} end={vec(0, 200)} colors={['#4338ca', '#22d3ee']} />
      </Path>
    </Canvas>
  );
}

6. Animating Skia values with Reanimated

React Native Skia was built from the ground up to work with Reanimated. Instead of changing props like color, radius, or path through regular React state, shared values can be passed directly as values into Skia components, so changes get redrawn without a React re-render and without a detour through the JS thread, straight on the UI thread.

That enables things like path interpolation, smoothly morphing between two shapes, driven by a single shared value between zero and one. Because both Reanimated and Skia sit on the same JSI foundation, this combination stays at 60 frames per second even for complex, data-driven animations.


const progress = useSharedValue(0);

const animatedRadius = useDerivedValue(() => {
  return interpolate(progress.value, [0, 1], [40, 90]);
});

<Canvas style={{ width: 200, height: 200 }}>
  <Circle cx={100} cy={100} r={animatedRadius} color="#4338ca" />
</Canvas>

7. How Skia renders: its own drawing path instead of a view hierarchy

Normal React Native rendering goes through Fabric's view hierarchy, with layout calculation, diffing, and creating individual native views. Skia, by contrast, draws directly onto a GPU-backed surface, without every primitive being its own native component. Changes to a Skia canvas therefore do not trigger a React reconciliation pass across hundreds of child components, only a fresh draw pass on the canvas itself.

That makes Skia particularly suited to scenarios with many small, frequently changing elements, such as particle effects, waveform visualizations, or real-time drawing, where a view-based approach would simply need too many individual layout passes to stay smooth.

8. Skia compared to SVG-based solutions

react-native-svg ultimately renders every SVG element through its own native view per node, which is perfectly fine for static or moderately complex illustrations and has the advantage of working directly with existing SVG assets from a design team. With many elements or frequent changes, say animated charts, that view-per-element approach becomes a bottleneck.

Skia, on the other hand, comes with a steeper learning curve, since paths and transforms are often built imperatively rather than purely declaratively, and it noticeably increases the app's native binary size. Switching pays off specifically where performance or custom drawing logic matters more than quickly wiring up ready-made icons.

9. When React Native Skia pays off, and when it does not

For a handful of static icons or a simple illustration, react-native-svg or a plain optimized PNG remains the simpler, lower-maintenance choice. Skia pays off where graphics are interactive, data-driven, or animated, say signature pads, custom charts, image filters, or drawing tools, where view-based approaches hit their limits.

A good rule of thumb: once more than a dozen graphic elements need to be drawn simultaneously on screen and recalculated regularly, it is worth taking a serious look at Skia. Below that, the extra effort usually is not justified.

Use case Recommended solution Why Effort
A handful of static icons react-native-svg or PNG No rendering overhead needed Low
Interactive signature pad React Native Skia Direct path access, high frame rate Medium
Custom chart with many data points React Native Skia One canvas draw call instead of hundreds of views Medium to high
Image filter or photo editor React Native Skia with shaders GPU-close shader pipeline available High
Simple illustration without interaction SVG or Lottie Declarative and lower maintenance Low
Particle effect or waveform React Native Skia Many elements without view overhead High

Mironsoft

React Native app development and Magento integration

A mobile app for the Magento shop that actually runs smoothly?

We build React Native apps cleanly connected to the Magento REST or GraphQL API, from the first line of code to publishing on the App Store and Google Play.

App Concept

Plan the architecture and feature scope of a Magento-connected app together.

Magento API Integration

Cleanly connect product catalog, cart, and checkout to the shop API.

Store Publishing

Guide the App Store and Google Play release process without pitfalls.

10. Summary

React Native Skia

Core idea

Skia draws all primitives onto one shared native surface instead of creating a view per element.

Core API

Skia.Path.Make() builds custom paths imperatively, similar to the browser's Canvas 2D API.

Reanimated combo

Shared values can be passed directly as Skia props and get redrawn without a React re-render.

Rule of thumb

Once about a dozen graphic elements change at once, switching from SVG to Skia is worth it.

11. FAQ: React Native Skia

1What is the difference between React Native Skia and react-native-svg?
react-native-svg creates a native view for every SVG element, while React Native Skia draws all primitives onto a single native surface. That makes Skia noticeably faster with many or frequently changing elements, while for simple static graphics the difference is barely noticeable.
2Do I need the New Architecture to use React Native Skia?
React Native Skia builds on JSI and generally works with both the old and the New Architecture, though current versions of the library clearly focus on the New Architecture with Fabric.
3How do I build a custom path in Skia?
Skia.Path.Make() creates an empty path object, which is then built point by point with methods like moveTo, lineTo, or cubicTo, similar to the Canvas 2D API in the browser.
4Can I export a Skia canvas as an image?
Yes, makeImageSnapshot() on a canvas reference exports the current content directly as a native image object, which can then be processed further, for example as a base64 string.
5Is Skia worth it for a simple app with only a few icons?
Usually not. For a handful of static graphic elements, react-native-svg or an optimized image format is easier to maintain. Skia pays off mainly for interactive or data-driven graphics.
6How do I combine Skia with Reanimated?
Shared values from Reanimated can be passed directly as values into Skia props or derived through useDerivedValue. Changes then get redrawn directly on the UI thread without a React re-render.
7Does React Native Skia noticeably increase app size?
Yes, the native Skia library gets added to the app and noticeably increases the binary size, usually by several megabytes, which should factor into the decision for or against Skia.
8Can I create shaders and gradients with Skia?
Yes, Skia ships shader support as a native concept, including LinearGradient, RadialGradient, and custom fragment shaders for effects like image filters or animated gradients.
9Is Skia better suited for signature pads than a plain touch-based view solution?
Yes, because on every touch move only the path is recalculated and a single draw command is executed, instead of creating a new native view for every point, which produces a noticeably smoother drawing feel.
10Does React Native Skia render identically on iOS and Android?
Yes, Skia renders across platforms using the same engine, so drawing results on iOS and Android are nearly pixel identical, which is often not guaranteed with native view-based solutions.