Skip to main content
Paths are the foundation for creating complex shapes in Skia. They can contain lines, curves, and multiple contours.

Creating Paths

Using Skia.Path

import { Skia } from "@shopify/react-native-skia";

const path = Skia.Path.Make();
path.moveTo(10, 10);
path.lineTo(100, 10);
path.lineTo(100, 100);
path.lineTo(10, 100);
path.close();

From SVG String

const path = Skia.Path.MakeFromSVGString(
  "M 10 10 L 100 10 L 100 100 L 10 100 Z"
);

Path Methods

Movement Commands

moveTo
(x: number, y: number) => SkPath
Starts a new contour at the specified point
path.moveTo(50, 50);
lineTo
(x: number, y: number) => SkPath
Draws a straight line to the specified point
path.lineTo(100, 100);
rMoveTo
(dx: number, dy: number) => SkPath
Relative move (offset from current position)
path.rMoveTo(20, 20);
rLineTo
(dx: number, dy: number) => SkPath
Relative line (offset from current position)
path.rLineTo(50, 0);

Curves

quadTo
(x1: number, y1: number, x2: number, y2: number) => SkPath
Draws a quadratic Bezier curve
path.moveTo(0, 100);
path.quadTo(50, 0, 100, 100); // control point, end point
cubicTo
(cp1x: number, cp1y: number, cp2x: number, cp2y: number, x: number, y: number) => SkPath
Draws a cubic Bezier curve
path.moveTo(0, 100);
path.cubicTo(25, 0, 75, 0, 100, 100);
conicTo
(x1: number, y1: number, x2: number, y2: number, w: number) => SkPath
Draws a conic curve with weight
path.conicTo(50, 0, 100, 100, 0.5);

Arcs

arcToOval
(oval: SkRect, startAngle: number, sweepAngle: number, forceMoveTo: boolean) => SkPath
Draws an arc within an oval
const oval = rect(0, 0, 100, 100);
path.arcToOval(oval, 0, 180, false);
arcToRotated
(rx: number, ry: number, xAxisRotate: number, useSmallArc: boolean, isCCW: boolean, x: number, y: number) => SkPath
Draws an SVG-style arc
path.moveTo(10, 50);
path.arcToRotated(40, 40, 0, false, true, 90, 50);

Shapes

addRect
(rect: SkRect, isCCW?: boolean) => SkPath
Adds a rectangle to the path
path.addRect(rect(10, 10, 100, 100));
addRRect
(rrect: SkRRect, isCCW?: boolean) => SkPath
Adds a rounded rectangle
path.addRRect(rrect(rect(10, 10, 100, 100), 10, 10));
addCircle
(x: number, y: number, r: number) => SkPath
Adds a circle
path.addCircle(50, 50, 40);
addOval
(oval: SkRect, isCCW?: boolean, startIndex?: number) => SkPath
Adds an ellipse
path.addOval(rect(0, 0, 100, 50));
addPoly
(points: SkPoint[], close: boolean) => SkPath
Adds a polygon from points
path.addPoly([
  { x: 50, y: 0 },
  { x: 100, y: 50 },
  { x: 50, y: 100 },
  { x: 0, y: 50 },
], true);

Path Operations

close
() => SkPath
Closes the current contour with a line to the start point
path.close();
reset
() => SkPath
Clears the path and releases memory
path.reset();
rewind
() => SkPath
Clears the path but keeps allocated memory
path.rewind(); // Faster for reuse
copy
() => SkPath
Creates a copy of the path
const pathCopy = path.copy();

Transformations

transform
(matrix: InputMatrix) => SkPath
Transforms the path by a matrix
const matrix = Skia.Matrix();
matrix.translate(50, 50);
matrix.rotate(Math.PI / 4);
const transformed = path.transform(matrix);
offset
(dx: number, dy: number) => SkPath
Translates the path
const moved = path.offset(20, 30);

Boolean Operations

op
(path: SkPath, op: PathOp) => boolean
Performs boolean operations on paths
import { PathOp } from "@shopify/react-native-skia";

const result = path1.op(path2, PathOp.Union);
Operations:
  • PathOp.Difference: Subtract path2 from path1
  • PathOp.Intersect: Intersection of both paths
  • PathOp.Union: Combination of both paths
  • PathOp.XOR: Exclusive OR
  • PathOp.ReverseDifference: Subtract path1 from path2
simplify
() => boolean
Simplifies overlapping contours
path.simplify();

Stroking

stroke
(opts?: StrokeOpts) => SkPath | null
Converts path to its stroked equivalent
const stroked = path.stroke({
  width: 5,
  join: StrokeJoin.Round,
  cap: StrokeCap.Round,
});
dash
(on: number, off: number, phase: number) => boolean
Applies dash pattern to path
path.dash(10, 5, 0);

Measurement

getBounds
() => SkRect
Gets the bounding box
const bounds = path.getBounds();
console.log(bounds); // { x, y, width, height }
computeTightBounds
() => SkRect
Gets tight bounds (more accurate but slower)
const tightBounds = path.computeTightBounds();
contains
(x: number, y: number) => boolean
Tests if a point is inside the path
const isInside = path.contains(50, 50);
isEmpty
() => boolean
Checks if path has no verbs
if (path.isEmpty()) {
  console.log("Path is empty");
}

Path Interpolation

interpolate
(end: SkPath, weight: number, output?: SkPath) => SkPath | null
Interpolates between two paths
const morphed = path1.interpolate(path2, 0.5);
isInterpolatable
(path: SkPath) => boolean
Checks if paths can be interpolated
if (path1.isInterpolatable(path2)) {
  const morphed = path1.interpolate(path2, 0.5);
}

Path Trimming

trim
(start: number, end: number, isComplement: boolean) => SkPath | null
Trims path to a segment (0-1)
const segment = path.trim(0.25, 0.75, false);

Fill Types

import { FillType } from "@shopify/react-native-skia";

path.setFillType(FillType.Winding); // Default
path.setFillType(FillType.EvenOdd);
path.setFillType(FillType.InverseWinding);
path.setFillType(FillType.InverseEvenOdd);

Path Component

import { Canvas, Path } from "@shopify/react-native-skia";

const path = Skia.Path.Make();
path.moveTo(50, 50);
path.lineTo(150, 50);
path.lineTo(100, 150);
path.close();

<Canvas style={{ flex: 1 }}>
  <Path path={path} color="blue" />
</Canvas>

Examples

Rounded Path

const path = Skia.Path.Make();
path.moveTo(50, 100);
path.lineTo(150, 100);
path.arcToRotated(25, 25, 0, false, true, 150, 150);
path.lineTo(50, 150);
path.arcToRotated(25, 25, 0, false, true, 50, 100);
path.close();

Star Shape

const star = () => {
  const path = Skia.Path.Make();
  const points = 5;
  const outerRadius = 50;
  const innerRadius = 20;
  
  for (let i = 0; i < points * 2; i++) {
    const angle = (i * Math.PI) / points - Math.PI / 2;
    const radius = i % 2 === 0 ? outerRadius : innerRadius;
    const x = 100 + Math.cos(angle) * radius;
    const y = 100 + Math.sin(angle) * radius;
    
    if (i === 0) {
      path.moveTo(x, y);
    } else {
      path.lineTo(x, y);
    }
  }
  
  path.close();
  return path;
};

Animated Path

import { useSharedValue, withRepeat, withTiming } from "react-native-reanimated";
import { Canvas, Path, Skia } from "@shopify/react-native-skia";

const path1 = Skia.Path.MakeFromSVGString("M 50 50 L 150 50 L 100 150 Z");
const path2 = Skia.Path.MakeFromSVGString("M 50 150 L 150 150 L 100 50 Z");

export default function MorphingPath() {
  const progress = useSharedValue(0);
  
  useEffect(() => {
    progress.value = withRepeat(
      withTiming(1, { duration: 2000 }),
      -1,
      true
    );
  }, []);
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Path
        path={() => path1.interpolate(path2, progress.value)}
        color="purple"
      />
    </Canvas>
  );
}

Build docs developers (and LLMs) love