Skip to main content
React Native Skia integrates seamlessly with Reanimated 2+ to create high-performance animations that run on the UI thread.

Basic Concept

Use Reanimated’s useSharedValue and animation functions with Skia components. The values update on the UI thread for smooth 60fps+ animations.
import { useSharedValue, withRepeat, withTiming } from "react-native-reanimated";
import { Canvas, Circle } from "@shopify/react-native-skia";
import { useEffect } from "react";

export default function AnimatedCircle() {
  const cx = useSharedValue(100);
  
  useEffect(() => {
    cx.value = withRepeat(
      withTiming(200, { duration: 1000 }),
      -1,
      true
    );
  }, []);
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Circle cx={cx} cy={150} r={50} color="blue" />
    </Canvas>
  );
}

Shared Values

useSharedValue

Create a value that can be animated on the UI thread:
import { useSharedValue } from "react-native-reanimated";

const x = useSharedValue(0);
const y = useSharedValue(0);
const rotation = useSharedValue(0);
const scale = useSharedValue(1);

Setting Values

// Direct assignment
x.value = 100;

// With animation
x.value = withTiming(100, { duration: 500 });
x.value = withSpring(100);
x.value = withDecay({ velocity: 1000 });

Animation Functions

withTiming

Animate to a value over time:
import { withTiming, Easing } from "react-native-reanimated";

// Basic timing
position.value = withTiming(100, { duration: 500 });

// With easing
position.value = withTiming(100, {
  duration: 500,
  easing: Easing.bezier(0.25, 0.1, 0.25, 1),
});

// With callback
position.value = withTiming(100, { duration: 500 }, (finished) => {
  if (finished) {
    console.log("Animation completed");
  }
});

withSpring

Animate with spring physics:
import { withSpring } from "react-native-reanimated";

// Default spring
position.value = withSpring(100);

// Custom spring config
position.value = withSpring(100, {
  damping: 10,
  mass: 1,
  stiffness: 100,
  overshootClamping: false,
  restDisplacementThreshold: 0.01,
  restSpeedThreshold: 2,
});

withRepeat

Repeat an animation:
import { withRepeat, withTiming } from "react-native-reanimated";

// Infinite loop
rotation.value = withRepeat(
  withTiming(Math.PI * 2, { duration: 2000 }),
  -1 // -1 for infinite
);

// Repeat 3 times with reverse
position.value = withRepeat(
  withTiming(200, { duration: 1000 }),
  3,
  true // reverse on alternate cycles
);

withSequence

Chain animations:
import { withSequence, withTiming, withDelay } from "react-native-reanimated";

opacity.value = withSequence(
  withTiming(1, { duration: 500 }),
  withDelay(1000, withTiming(0, { duration: 500 }))
);

withDecay

Animate with decay (deceleration):
import { withDecay } from "react-native-reanimated";

position.value = withDecay({
  velocity: 1000,
  deceleration: 0.998,
});

Derived Values

useDerivedValue

Compute values based on other shared values:
import { useDerivedValue } from "react-native-reanimated";

const progress = useSharedValue(0);

const scale = useDerivedValue(() => {
  return 1 + progress.value * 0.5;
});

const rotation = useDerivedValue(() => {
  return progress.value * Math.PI * 2;
});

Gestures

Combine with Reanimated gestures:
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { useSharedValue } from "react-native-reanimated";
import { Canvas, Circle } from "@shopify/react-native-skia";

export default function DraggableCircle() {
  const x = useSharedValue(150);
  const y = useSharedValue(150);
  
  const gesture = Gesture.Pan()
    .onChange((e) => {
      x.value += e.changeX;
      y.value += e.changeY;
    });
  
  return (
    <GestureDetector gesture={gesture}>
      <Canvas style={{ flex: 1 }}>
        <Circle cx={x} cy={y} r={50} color="red" />
      </Canvas>
    </GestureDetector>
  );
}

Using onSize

Get canvas dimensions reactively:
import { useSharedValue } from "react-native-reanimated";
import { Canvas, Circle } from "@shopify/react-native-skia";

export default function ResponsiveCircle() {
  const size = useSharedValue({ width: 0, height: 0 });
  
  return (
    <Canvas style={{ flex: 1 }} onSize={size}>
      <Circle
        cx={() => size.value.width / 2}
        cy={() => size.value.height / 2}
        r={50}
        color="blue"
      />
    </Canvas>
  );
}

Worklet Functions

Use worklet functions for complex animations:
import { useDerivedValue } from "react-native-reanimated";

const progress = useSharedValue(0);

const animatedPath = useDerivedValue(() => {
  "worklet";
  const path = Skia.Path.Make();
  const r = 100 + progress.value * 50;
  path.addCircle(150, 150, r);
  return path;
});

<Path path={animatedPath} color="green" />

Examples

Fade In Animation

import { useSharedValue, withTiming } from "react-native-reanimated";
import { useEffect } from "react";
import { Canvas, Circle } from "@shopify/react-native-skia";

export default function FadeIn() {
  const opacity = useSharedValue(0);
  
  useEffect(() => {
    opacity.value = withTiming(1, { duration: 1000 });
  }, []);
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Circle cx={150} cy={150} r={100} color="blue" opacity={opacity} />
    </Canvas>
  );
}

Continuous Rotation

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

export default function RotatingRect() {
  const rotation = useSharedValue(0);
  
  useEffect(() => {
    rotation.value = withRepeat(
      withTiming(Math.PI * 2, { duration: 2000 }),
      -1
    );
  }, []);
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Rect
        x={0}
        y={0}
        width={100}
        height={100}
        color="red"
        origin={{ x: 50, y: 50 }}
        transform={[{ rotate: rotation }]}
      />
    </Canvas>
  );
}

Bouncing Animation

import { useSharedValue, withRepeat, withSequence, withTiming } from "react-native-reanimated";
import { useEffect } from "react";
import { Canvas, Circle } from "@shopify/react-native-skia";

export default function BouncingBall() {
  const y = useSharedValue(100);
  
  useEffect(() => {
    y.value = withRepeat(
      withSequence(
        withTiming(300, { duration: 500 }),
        withTiming(100, { duration: 500 })
      ),
      -1
    );
  }, []);
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Circle cx={150} cy={y} r={30} color="orange" />
    </Canvas>
  );
}

Interactive Scale

import { Gesture, GestureDetector } from "react-native-gesture-handler";
import { useSharedValue, withSpring } from "react-native-reanimated";
import { Canvas, Circle } from "@shopify/react-native-skia";

export default function ScalableCircle() {
  const scale = useSharedValue(1);
  const savedScale = useSharedValue(1);
  
  const pinch = Gesture.Pinch()
    .onUpdate((e) => {
      scale.value = savedScale.value * e.scale;
    })
    .onEnd(() => {
      savedScale.value = scale.value;
    });
  
  return (
    <GestureDetector gesture={pinch}>
      <Canvas style={{ flex: 1 }}>
        <Circle
          cx={150}
          cy={150}
          r={() => 50 * scale.value}
          color="purple"
        />
      </Canvas>
    </GestureDetector>
  );
}

Performance Tips

  • All animations run on the UI thread - no bridge crossing
  • Use worklet functions for complex calculations
  • Avoid using JavaScript values inside worklets
  • Mark functions with "worklet" directive when needed
  • Use useDerivedValue for computed values
  • Batch updates when animating multiple values

Build docs developers (and LLMs) love