Skip to main content
React Native Skia provides powerful animation capabilities through seamless integration with Reanimated 3, enabling high-performance 60+ FPS animations with zero bridge overhead.

Why Reanimated?

React Native Skia integrates with Reanimated 3 for animations because:
  • Zero bridge overhead - Animations run entirely on the UI thread
  • 60+ FPS performance - Smooth animations without dropping frames
  • Declarative API - Easy to use and understand
  • Shared values - Reactive state that updates the canvas automatically
  • Gesture integration - Works seamlessly with React Native Gesture Handler

Basic Animation Example

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

const AnimatedCircle = () => {
  const cx = useSharedValue(50);

  useEffect(() => {
    cx.value = withRepeat(
      withTiming(200, { duration: 1000 }),
      -1,  // infinite
      true // reverse
    );
  }, [cx]);

  return (
    <Canvas style={{ width: 256, height: 256 }}>
      <Circle cx={cx} cy={128} r={40} color="blue" />
    </Canvas>
  );
};

Shared Values

Shared values are the foundation of Reanimated animations. They can be used directly in Skia components:
import { useSharedValue } from "react-native-reanimated";

const rotation = useSharedValue(0);
const opacity = useSharedValue(1);
const color = useSharedValue("red");

// Use in components
<Group transform={[{ rotate: rotation }]}>
  <Circle cx={128} cy={128} r={64} color={color} opacity={opacity} />
</Group>

Animation Types

Timing Animation

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

useEffect(() => {
  opacity.value = withTiming(0, { 
    duration: 500,
    easing: Easing.inOut(Easing.ease)
  });
}, []);

Spring Animation

Physics-based spring animation:
import { withSpring } from "react-native-reanimated";

useEffect(() => {
  scale.value = withSpring(1.5, {
    damping: 10,
    stiffness: 100
  });
}, []);

Decay Animation

Gradually slow down (great for gestures):
import { withDecay } from "react-native-reanimated";

const onPanEnd = (velocityX: number) => {
  translateX.value = withDecay({
    velocity: velocityX,
    clamp: [0, 300]
  });
};

Repeat Animation

Repeat animations indefinitely or a set number of times:
import { withRepeat, withTiming } from "react-native-reanimated";

useEffect(() => {
  // Infinite loop
  rotation.value = withRepeat(
    withTiming(360, { duration: 2000 }),
    -1,  // -1 for infinite
    false // don't reverse
  );
}, []);

Sequence Animation

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

useEffect(() => {
  scale.value = withSequence(
    withTiming(1.2, { duration: 200 }),
    withTiming(1, { duration: 200 })
  );
}, []);

Derived Values

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

const progress = useSharedValue(0);

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

const opacity = useDerivedValue(() => {
  return 1 - progress.value;
});

return (
  <Group transform={[{ rotate: rotation }]} opacity={opacity}>
    <Circle cx={128} cy={128} r={40} color="blue" />
  </Group>
);

Interpolation

Map values from one range to another:
import { interpolate, Extrapolation } from "react-native-reanimated";

const scrollY = useSharedValue(0);

const opacity = useDerivedValue(() => {
  return interpolate(
    scrollY.value,
    [0, 100],      // input range
    [1, 0],        // output range
    Extrapolation.CLAMP
  );
});

Color Interpolation

import { interpolateColor } from "react-native-reanimated";

const progress = useSharedValue(0);

const color = useDerivedValue(() => {
  return interpolateColor(
    progress.value,
    [0, 1],
    ["#FF0000", "#0000FF"]
  );
});

<Circle cx={128} cy={128} r={64} color={color} />

Transform Animations

const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const rotation = useSharedValue(0);
const scale = useSharedValue(1);

<Group 
  transform={[
    { translateX },
    { translateY },
    { rotate: rotation },
    { scale }
  ]}
>
  <Circle cx={128} cy={128} r={40} color="purple" />
</Group>

Path Interpolation

Interpolate between paths:
import { Skia } from "@shopify/react-native-skia";
import { useDerivedValue } from "react-native-reanimated";

const progress = useSharedValue(0);

const path1 = Skia.Path.Make();
path1.addCircle(128, 128, 64);

const path2 = Skia.Path.Make();
path2.addRect(Skia.XYWHRect(64, 64, 128, 128));

const animatedPath = useDerivedValue(() => {
  return path1.interpolate(path2, progress.value);
});

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

Performance Tips

Use worklets

Mark functions that run on the UI thread with 'worklet':
const onFrame = (frameInfo: FrameInfo) => {
  'worklet';
  rotation.value = frameInfo.timestamp * 0.001;
};

Minimize re-renders

Shared values don’t trigger re-renders. Use them for high-frequency updates:
// Good - no re-renders
const rotation = useSharedValue(0);

// Bad - causes re-renders on every change
const [rotation, setRotation] = useState(0);

Batch updates

Update multiple values in a single animation:
const animate = () => {
  'worklet';
  x.value = withTiming(100);
  y.value = withTiming(100);
  rotation.value = withTiming(Math.PI);
};

requestAnimationFrame

For custom animation loops:
import { useFrameCallback } from "react-native-reanimated";

const rotation = useSharedValue(0);

useFrameCallback((frameInfo) => {
  rotation.value = (frameInfo.timestamp / 1000) % (Math.PI * 2);
});

Common Animation Patterns

Fade In

const opacity = useSharedValue(0);

useEffect(() => {
  opacity.value = withTiming(1, { duration: 300 });
}, []);

Pulse Effect

const scale = useSharedValue(1);

useEffect(() => {
  scale.value = withRepeat(
    withSequence(
      withTiming(1.2, { duration: 500 }),
      withTiming(1, { duration: 500 })
    ),
    -1,
    false
  );
}, []);

Rotate Continuously

const rotation = useSharedValue(0);

useEffect(() => {
  rotation.value = withRepeat(
    withTiming(2 * Math.PI, { duration: 2000, easing: Easing.linear }),
    -1,
    false
  );
}, []);

Bounce In

const scale = useSharedValue(0);

useEffect(() => {
  scale.value = withSpring(1, {
    damping: 5,
    stiffness: 100
  });
}, []);

Animation State

Cancel animations programmatically:
import { cancelAnimation } from "react-native-reanimated";

const stopAnimation = () => {
  cancelAnimation(rotation);
};

Build docs developers (and LLMs) love