|
| 1 | +--- |
| 2 | +id: skottie |
| 3 | +title: Skottie |
| 4 | +sidebar_label: Skottie |
| 5 | +slug: /skottie |
| 6 | +--- |
| 7 | + |
| 8 | +Skottie is a Lottie animation renderer built on Skia. It allows you to load and render After Effects animations exported via Bodymovin/Lottie in React Native Skia. |
| 9 | +It provides a powerful way to integrate After Effects animations into your React Native Skia applications with full programmatic control over animation properties. |
| 10 | + |
| 11 | +## Rendering Animations |
| 12 | + |
| 13 | +### Using the Skottie Component |
| 14 | + |
| 15 | +React Native Skia provides a `Skottie` component for easy integration: |
| 16 | + |
| 17 | +```tsx twoslash |
| 18 | +import React from "react"; |
| 19 | +import { Canvas, Group, Skottie, Skia } from "@shopify/react-native-skia"; |
| 20 | + |
| 21 | +const legoAnimationJSON = require("./assets/lego_loader.json"); |
| 22 | +const animation = Skia.Skottie.Make(JSON.stringify(legoAnimationJSON)); |
| 23 | + |
| 24 | +const SkottieExample = () => { |
| 25 | + return ( |
| 26 | + <Canvas style={{ width: 400, height: 300 }}> |
| 27 | + <Group transform={[{ scale: 0.5 }]}> |
| 28 | + <Skottie animation={animation} frame={41} /> |
| 29 | + </Group> |
| 30 | + </Canvas> |
| 31 | + ); |
| 32 | +}; |
| 33 | +``` |
| 34 | + |
| 35 | +### Animated Playback with Reanimated |
| 36 | + |
| 37 | +For smooth animation playback, combine Skottie with React Native Reanimated: |
| 38 | + |
| 39 | +```tsx twoslash |
| 40 | +import React from "react"; |
| 41 | +import { |
| 42 | + Skia, |
| 43 | + Canvas, |
| 44 | + useClock, |
| 45 | + Group, |
| 46 | + Skottie, |
| 47 | +} from "@shopify/react-native-skia"; |
| 48 | +import { useDerivedValue } from "react-native-reanimated"; |
| 49 | + |
| 50 | +const legoAnimationJSON = require("./assets/lego_loader.json"); |
| 51 | +const animation = Skia.Skottie.Make(JSON.stringify(legoAnimationJSON)); |
| 52 | + |
| 53 | +const AnimatedSkottieExample = () => { |
| 54 | + const clock = useClock(); |
| 55 | + const frame = useDerivedValue(() => { |
| 56 | + const fps = animation.fps(); |
| 57 | + const duration = animation.duration(); |
| 58 | + const currentFrame = |
| 59 | + Math.floor((clock.value / 1000) * fps) % (duration * fps); |
| 60 | + return currentFrame; |
| 61 | + }); |
| 62 | + |
| 63 | + return ( |
| 64 | + <Canvas style={{ flex: 1 }}> |
| 65 | + <Group transform={[{ scale: 0.5 }]}> |
| 66 | + <Skottie animation={animation} frame={frame} /> |
| 67 | + </Group> |
| 68 | + </Canvas> |
| 69 | + ); |
| 70 | +}; |
| 71 | +``` |
| 72 | + |
| 73 | +### Basic Rendering |
| 74 | + |
| 75 | +```tsx twoslash |
| 76 | +import { Skia, Canvas } from "@shopify/react-native-skia"; |
| 77 | +const animation = {} as any; |
| 78 | +// ---cut--- |
| 79 | +const surface = Skia.Surface.MakeOffscreen(800, 600); |
| 80 | +if (!surface) { |
| 81 | + throw new Error("Failed to create surface"); |
| 82 | +} |
| 83 | +const canvas = surface.getCanvas(); |
| 84 | + |
| 85 | +// Seek to a specific frame |
| 86 | +animation.seekFrame(41); |
| 87 | + |
| 88 | +// Render the animation |
| 89 | +animation.render(canvas); |
| 90 | + |
| 91 | +surface.flush(); |
| 92 | +const image = surface.makeImageSnapshot(); |
| 93 | +``` |
| 94 | + |
| 95 | +## Creating a Skottie Animation |
| 96 | + |
| 97 | +To create a Skottie animation, use `Skia.Skottie.Make()` with your Lottie JSON data: |
| 98 | + |
| 99 | +```tsx twoslash |
| 100 | +import { Skia } from "@shopify/react-native-skia"; |
| 101 | + |
| 102 | +const legoAnimationJSON = require("./assets/lego_loader.json"); |
| 103 | + |
| 104 | +const animation = Skia.Skottie.Make(JSON.stringify(legoAnimationJSON)); |
| 105 | +``` |
| 106 | + |
| 107 | +### With Assets |
| 108 | + |
| 109 | +Many Lottie animations include external assets like fonts and images. You can provide these when creating the animation: |
| 110 | + |
| 111 | +```tsx twoslash |
| 112 | +import { Skia } from "@shopify/react-native-skia"; |
| 113 | + |
| 114 | +const basicSlotsJSON = require("./assets/basic_slots.json"); |
| 115 | + |
| 116 | +const assets = { |
| 117 | + NotoSerif: Skia.Data.fromBytes(new Uint8Array([])), |
| 118 | + "img_0.png": Skia.Data.fromBytes(new Uint8Array([])), |
| 119 | +}; |
| 120 | + |
| 121 | +const animation = Skia.Skottie.Make(JSON.stringify(basicSlotsJSON), assets); |
| 122 | +``` |
| 123 | + |
| 124 | +## Animation Properties |
| 125 | + |
| 126 | +### Basic Information |
| 127 | + |
| 128 | +Get basic information about your animation: |
| 129 | + |
| 130 | +```tsx twoslash |
| 131 | +import { Skia } from "@shopify/react-native-skia"; |
| 132 | +const animation = {} as any; |
| 133 | +// ---cut--- |
| 134 | +// Duration in seconds |
| 135 | +const duration = animation.duration(); |
| 136 | + |
| 137 | +// Frames per second |
| 138 | +const fps = animation.fps(); |
| 139 | + |
| 140 | +// Lottie version |
| 141 | +const version = animation.version(); |
| 142 | + |
| 143 | +// Animation dimensions |
| 144 | +const size = animation.size(); // { width: 800, height: 600 } |
| 145 | +``` |
| 146 | + |
| 147 | +## Slot Management |
| 148 | + |
| 149 | +Slots are placeholders built into the design of Lottie animations that allow for dynamic content replacement at runtime. This is incredibly convenient for customizing animations without recreating them - designers can create slots in After Effects, and developers can programmatically replace colors, text, images, and other properties. |
| 150 | + |
| 151 | +Skottie supports slots for dynamic content replacement: |
| 152 | + |
| 153 | +### Getting Slot Information |
| 154 | + |
| 155 | +```tsx twoslash |
| 156 | +const animation = {} as any; |
| 157 | +// ---cut--- |
| 158 | +const slotInfo = animation.getSlotInfo(); |
| 159 | +// Returns: |
| 160 | +// { |
| 161 | +// colorSlotIDs: ["FillsGroup", "StrokeGroup"], |
| 162 | +// imageSlotIDs: ["ImageSource"], |
| 163 | +// scalarSlotIDs: ["Opacity"], |
| 164 | +// textSlotIDs: ["TextSource"], |
| 165 | +// vec2SlotIDs: ["ScaleGroup"] |
| 166 | +// } |
| 167 | +``` |
| 168 | + |
| 169 | +### Setting Color Slots |
| 170 | + |
| 171 | +```tsx twoslash |
| 172 | +import { Skia } from "@shopify/react-native-skia"; |
| 173 | +const animation = {} as any; |
| 174 | +// ---cut--- |
| 175 | +animation.setColorSlot("FillsGroup", Skia.Color("cyan")); |
| 176 | +animation.setColorSlot("StrokeGroup", Skia.Color("magenta")); |
| 177 | +``` |
| 178 | + |
| 179 | +## Property Access and Modification |
| 180 | + |
| 181 | +Beyond slots, Skottie provides powerful introspection capabilities that allow you to modify virtually any property of the animation at runtime. This gives you complete programmatic control over colors, text, opacity, transforms, and more - making it possible to create highly dynamic and interactive animations. |
| 182 | + |
| 183 | +### Color Properties |
| 184 | + |
| 185 | +```tsx twoslash |
| 186 | +import { Skia } from "@shopify/react-native-skia"; |
| 187 | +const animation = {} as any; |
| 188 | +// ---cut--- |
| 189 | +// Get all color properties |
| 190 | +const colorProps = animation.getColorProps(); |
| 191 | +// Returns array of: { key: string, value: Float32Array } |
| 192 | + |
| 193 | +// Set a specific color property |
| 194 | +const colorProp = colorProps[0]; |
| 195 | +animation.setColor(colorProp.key, Skia.Color("rgb(60, 120, 255)")); |
| 196 | +``` |
| 197 | + |
| 198 | +### Text Properties |
| 199 | + |
| 200 | +```tsx twoslash |
| 201 | +const animation = {} as any; |
| 202 | +// ---cut--- |
| 203 | +// Get all text properties |
| 204 | +const textProps = animation.getTextProps(); |
| 205 | +// Returns array of: { key: string, value: { text: string, size: number } } |
| 206 | + |
| 207 | +// Set text content |
| 208 | +animation.setText("hello!", "World", 164); |
| 209 | +``` |
| 210 | + |
| 211 | +### Opacity Properties |
| 212 | + |
| 213 | +```tsx twoslash |
| 214 | +const animation = {} as any; |
| 215 | +// ---cut--- |
| 216 | +// Get all opacity properties |
| 217 | +const opacityProps = animation.getOpacityProps(); |
| 218 | +// Returns array of: { key: string, value: number } |
| 219 | +``` |
| 220 | + |
| 221 | +### Transform Properties |
| 222 | + |
| 223 | +```tsx twoslash |
| 224 | +const animation = {} as any; |
| 225 | +// ---cut--- |
| 226 | +// Get all transform properties |
| 227 | +const transformProps = animation.getTransformProps(); |
| 228 | +// Returns array of: { |
| 229 | +// key: string, |
| 230 | +// value: { |
| 231 | +// anchor: { x: number, y: number }, |
| 232 | +// position: { x: number, y: number }, |
| 233 | +// scale: { x: number, y: number }, |
| 234 | +// rotation: number, |
| 235 | +// skew: number, |
| 236 | +// skewAxis: number |
| 237 | +// } |
| 238 | +// } |
| 239 | +``` |
| 240 | + |
| 241 | +## Complete Example |
| 242 | + |
| 243 | +Here's a complete example showing how to load and render a Skottie animation with dynamic properties: |
| 244 | + |
| 245 | +```tsx twoslash |
| 246 | +import React, { useEffect, useState } from "react"; |
| 247 | +import { View } from "react-native"; |
| 248 | +import { |
| 249 | + Canvas, |
| 250 | + Skia, |
| 251 | + useCanvasRef, |
| 252 | + Image, |
| 253 | + SkSkottieAnimation, |
| 254 | + SkImage |
| 255 | +} from "@shopify/react-native-skia"; |
| 256 | + |
| 257 | +const animationJSON = require("./assets/fingerprint.json"); |
| 258 | + |
| 259 | +const SkottiePlayer = () => { |
| 260 | + const [animation, setAnimation] = useState<SkSkottieAnimation | null>(null); |
| 261 | + const [image, setImage] = useState<SkImage | null>(null); |
| 262 | + |
| 263 | + useEffect(() => { |
| 264 | + const skottieAnimation = Skia.Skottie.Make(JSON.stringify(animationJSON)); |
| 265 | + if (!skottieAnimation) { |
| 266 | + throw new Error("Failed to create animation"); |
| 267 | + } |
| 268 | + setAnimation(skottieAnimation); |
| 269 | + |
| 270 | + // Get animation properties |
| 271 | + const colorProps = skottieAnimation.getColorProps(); |
| 272 | + if (colorProps.length > 0) { |
| 273 | + // Change the first color property |
| 274 | + skottieAnimation.setColor(colorProps[0].key, Skia.Color("rgb(60, 120, 255)")); |
| 275 | + } |
| 276 | + |
| 277 | + // Render a frame |
| 278 | + const size = skottieAnimation.size(); |
| 279 | + const surface = Skia.Surface.MakeOffscreen(size.width, size.height); |
| 280 | + if (!surface) { |
| 281 | + throw new Error("Failed to create surface"); |
| 282 | + } |
| 283 | + const canvas = surface.getCanvas(); |
| 284 | + |
| 285 | + skottieAnimation.seekFrame(120); |
| 286 | + skottieAnimation.render(canvas); |
| 287 | + surface.flush(); |
| 288 | + |
| 289 | + const snapshot = surface.makeImageSnapshot(); |
| 290 | + setImage(snapshot); |
| 291 | + }, []); |
| 292 | + |
| 293 | + if (!image) return <View />; |
| 294 | + |
| 295 | + return ( |
| 296 | + <Canvas style={{ width: 400, height: 400 }}> |
| 297 | + <Image |
| 298 | + image={image} |
| 299 | + x={0} |
| 300 | + y={0} |
| 301 | + width={400} |
| 302 | + height={400} |
| 303 | + fit="contain" |
| 304 | + /> |
| 305 | + </Canvas> |
| 306 | + ); |
| 307 | +}; |
| 308 | +``` |
0 commit comments