Mobile animation skia
Skill agents-inc/skills/dist/plugins/mobile-animation-skia/skills/mobile-animation-skia
The official skills marketplace for Agents Inc, an agent composition framework that builds stacks and compiles specialized subagents for Claude Code
npx -y skills add agents-inc/skills --skill mobile-animation-skiaAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
React Native Skia GPU-accelerated 2D graphics - Canvas, declarative drawing, shaders, image filters, Paragraph text, Atlas batch rendering, Reanimated animations
SKILL.md
18.8 KB, as published. Nobody here has run it
React Native Skia Patterns
Quick Guide: Use
@shopify/react-native-skiafor GPU-accelerated 2D drawing in React Native. The Canvas component hosts a separate React renderer. Drawing primitives (Circle, Rect, Path, Image) compose declaratively. Paint attributes cascade through Groups. Animations use Reanimated shared values passed directly as props -- nocreateAnimatedComponentneeded. UseinterpolateColorsfrom Skia for color animations (not Reanimated'sinterpolateColor). Use Atlas for batch rendering thousands of sprites. Use Paragraph for rich text layout. Group transforms default to top-left origin, not center.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST pass Reanimated shared values directly as Skia component props -- do NOT use createAnimatedComponent or useAnimatedProps)
(You MUST use interpolateColors from @shopify/react-native-skia for color animations -- Reanimated's interpolateColor uses a different internal color format and produces wrong results)
(You MUST use the layer property to apply paint effects to Paragraph, Picture, and ImageSVG components -- they do not follow standard paint inheritance rules)
(You MUST remember that Group transform origin defaults to top-left, not center -- set origin prop explicitly for center-based rotations)
</critical_requirements>
Auto-detection: react-native-skia, @shopify/react-native-skia, Canvas, Skia.Path, Skia.Paint, Circle, Rect, Path, Group, Paint, ImageSVG, BackdropBlur, BackdropFilter, RuntimeShader, Atlas, Paragraph, ParagraphBuilder, usePathInterpolation, useClock, useRSXformBuffer, SkSL, image filter, shader, offscreen, Picture, FitBox
When to use:
- Drawing custom 2D graphics (charts, diagrams, custom shapes)
- Applying GPU-accelerated blur, shadow, or color filter effects
- Animating paths, shapes, or shader uniforms at 60 FPS
- Rendering rich text with mixed fonts using Paragraph API
- Batch rendering sprites/tiles with Atlas
- Creating custom image filters with SkSL shaders
- Generating images offscreen (thumbnails, exports)
When NOT to use:
- Standard UI layouts (use regular React Native views)
- Simple static images (use
<Image>from React Native) - 3D graphics (Skia is 2D only -- use a 3D solution)
- Text-only screens (use React Native
<Text>)
Key patterns covered:
- Canvas setup and threading model
- Declarative drawing with shapes, paths, and images
- Paint inheritance and composition through Groups
- Reanimated integration (shared values as props, color interpolation)
- Image filters (Blur, Shadow, RuntimeShader) and backdrop filters
- Paragraph text layout with custom fonts
- Atlas batch rendering for sprites and tiles
- SkSL custom shaders with uniforms
- Picture recording for dynamic drawing operations
- SVG rendering with limitations
Detailed Resources:
- examples/core.md - Canvas, shapes, paths, paint, groups, images
- examples/animations.md - Reanimated integration, path interpolation, color animation, Atlas animation
- examples/effects.md - Image filters, backdrop blur, shaders, RuntimeShader
- examples/text-and-media.md - Paragraph API, SVG rendering, Picture recording, offscreen rendering
- reference.md - Decision frameworks, paint property reference, SkSL types
<philosophy>
Philosophy
React Native Skia brings Skia (the graphics engine behind Chrome, Android, and Flutter) to React Native. It provides a separate React renderer inside the Canvas component -- you write JSX, but it renders to a GPU-accelerated Skia surface, not native views.
Core mental model:
- Canvas is the boundary -- everything inside
<Canvas>uses Skia's renderer, everything outside is regular React Native - Declarative by default -- compose shapes, paints, and effects as JSX children; use imperative API (
Skia.Path(),Skia.Paint()) only when you need dynamic construction - Paint cascades through Groups -- a Group's color, opacity, shader, or filter applies to all children unless overridden (exception: Paragraph, Picture, and ImageSVG need the
layerproperty) - Animations run on UI thread -- pass Reanimated shared values directly as props; Skia reads them on the UI thread with zero bridge cost
- Hybrid architecture -- use Skia canvases only where custom visuals are needed; standard views handle layout and navigation
When to reach for Skia:
- Custom graphics that native views cannot express (gradients on paths, blur effects, charts)
- Performance-critical animations that must run on the GPU
- Batch rendering (Atlas for thousands of sprites)
- Rich text layout needing mixed fonts or decorations (Paragraph)
- Custom image processing (RuntimeShader with SkSL)
When NOT to reach for Skia:
- Standard UI (buttons, lists, forms) -- native views are simpler and more accessible
- Simple images -- React Native's
<Image>is sufficient - Accessibility-critical content -- Skia canvas elements are not accessible to screen readers; overlay native views for accessibility
<patterns>
Core Patterns
Pattern 1: Canvas and Basic Shapes
Canvas is the root Skia drawing surface. It behaves like a regular React Native view (accepts style), but hosts its own React renderer internally.
import { Canvas, Circle, Rect, RoundedRect, Line } from "@shopify/react-native-skia";
const CANVAS_SIZE = 256;
const CIRCLE_RADIUS = 50;
<Canvas style={{ width: CANVAS_SIZE, height: CANVAS_SIZE }}>
<Circle cx={128} cy={128} r={CIRCLE_RADIUS} color="cyan" />
<Rect x={10} y={10} width={100} height={80} color="red" />
<RoundedRect x={10} y={120} width={100} height={80} r={16} color="blue" />
<Line p1={{ x: 0, y: 0 }} p2={{ x: 256, y: 256 }} color="green" strokeWidth={2} style="stroke" />
</Canvas>
Why good: declarative JSX, paint properties (color, style, strokeWidth) applied directly as props
See examples/core.md for Canvas props (onSize, androidWarmup, snapshots) and all shape primitives.
Pattern 2: Paint Inheritance and Groups
Groups apply paint attributes (color, opacity, shaders, filters) to all children. Groups also provide transforms, clipping, and z-ordering.
import { Canvas, Group, Circle, Rect } from "@shopify/react-native-skia";
<Canvas style={{ width: 256, height: 256 }}>
<Group color="blue" opacity={0.5}>
<Circle cx={80} cy={128} r={40} />
<Rect x={140} y={88} width={80} height={80} />
</Group>
</Canvas>
Why good: color and opacity cascade to both children without repetition, Group provides single point for transforms and clipping
Gotcha: Paragraph, Picture, and ImageSVG do not follow standard paint inheritance. Apply effects via the layer property instead.
See examples/core.md for transforms, clipping, and the layer escape hatch.
Pattern 3: Paths (Declarative and Imperative)
Use the declarative <Path> component with SVG path strings for static paths. Use Skia.Path() imperatively for dynamic path construction.
import { Canvas, Path, Skia } from "@shopify/react-native-skia";
// Declarative: SVG path string
<Path path="M 10 80 Q 95 10 180 80" color="purple" style="stroke" strokeWidth={3} />
// Imperative: dynamic construction
const path = Skia.Path.Make();
path.moveTo(10, 80);
path.quadTo(95, 10, 180, 80);
path.close();
When to use imperative: dynamic shapes computed at runtime (e.g., chart data), paths that change based on user input, paths needed outside JSX (worklets, offscreen)
See examples/core.md for path operations (dash effects, trim, boolean ops).
Pattern 4: Reanimated Integration
Pass Reanimated shared values directly as Skia component props. No createAnimatedComponent or useAnimatedProps needed -- Skia reads shared values on the UI thread automatically.
import { Canvas, Circle } from "@shopify/react-native-skia";
import { useSharedValue, withRepeat, withTiming } from "react-native-reanimated";
import { useEffect } from "react";
const DURATION = 2000;
const MIN_RADIUS = 20;
const MAX_RADIUS = 100;
export function PulsingCircle() {
const r = useSharedValue(MIN_RADIUS);
useEffect(() => {
r.value = withRepeat(withTiming(MAX_RADIUS, { duration: DURATION }), -1, true);
}, []);
return (
<Canvas style={{ flex: 1 }}>
<Circle cx={128} cy={128} r={r} color="cyan" />
</Canvas>
);
}
Why good: shared value r passed directly as prop, animation runs entirely on UI thread, zero bridge communication, 60 FPS
Critical: Use interpolateColors from @shopify/react-native-skia for color animations -- Reanimated's interpolateColor produces wrong results with Skia's internal color format.
See examples/animations.md for path interpolation, color animation, derived values, and Atlas animation.
Pattern 5: Image Filters and Backdrop Blur
Image filters (Blur, Shadow, ColorMatrix) apply as children to shapes or Groups. BackdropBlur applies blur to content behind a clipping mask (like CSS backdrop-filter).
import { Canvas, Image, Blur, BackdropBlur, Fill, useImage } from "@shopify/react-native-skia";
const BLUR_RADIUS = 10;
const BACKDROP_BLUR = 4;
export function BlurExample() {
const image = useImage(require("./photo.png"));
if (!image) return null;
return (
<Canvas style={{ width: 256, height: 256 }}>
<Image image={image} fit="cover" x={0} y={0} width={256} height={256}>
<Blur blur={BLUR_RADIUS} mode="clamp" />
</Image>
<BackdropBlur
blur={BACKDROP_BLUR}
clip={{ x: 0, y: 128, width: 256, height: 128 }}
>
<Fill color="rgba(0, 0, 0, 0.3)" />
</BackdropBlur>
</Canvas>
);
}
Why good: Blur as child applies to image only, BackdropBlur applies to content behind the clipping region, composable
See examples/effects.md for Shadow, ColorMatrix, RuntimeShader, and composed filter chains.
Pattern 6: Custom Shaders (SkSL)
Write GPU shaders in SkSL (Skia's GLSL-like language). Use Skia.RuntimeEffect.Make() to compile shaders. Pass uniforms as a plain object.
import { Canvas, Shader, Fill, Skia } from "@shopify/react-native-skia";
const SHADER_SOURCE = `
uniform float2 iResolution;
uniform float iTime;
vec4 main(vec2 pos) {
vec2 uv = pos / iResolution;
float wave = sin(uv.x * 10.0 + iTime * 2.0) * 0.5 + 0.5;
return vec4(uv.x, wave, uv.y, 1.0);
}
`;
const effect = Skia.RuntimeEffect.Make(SHADER_SOURCE)!;
// In component: pass animated shared values as uniforms
<Canvas style={{ flex: 1 }}>
<Fill>
<Shader source={effect} uniforms={{ iResolution: [256, 256], iTime: time }} />
</Fill>
</Canvas>
Key SkSL differences from GLSL: use .eval(xy) instead of sample() for child shaders, uniform shader for child shader declarations, supported uniform types: float, float2-float4, int, int2-int4, matrices, and arrays.
See examples/effects.md for RuntimeShader as image filter, child shaders, and pixel density considerations.
Pattern 7: Paragraph Text Layout
The Paragraph API handles rich text with mixed fonts, line breaking, and alignment. Requires building text with ParagraphBuilder. Paragraph does not follow standard paint inheritance -- use layer for effects.
import { Canvas, Paragraph, Skia, useFonts, TextAlign } from "@shopify/react-native-skia";
const PARAGRAPH_WIDTH = 300;
export function RichText() {
const fonts = useFonts({ Roboto: [require("./Roboto-Regular.ttf")] });
if (!fonts) return null;
const para = Skia.ParagraphBuilder.Make({ textAlign: TextAlign.Center }, fonts)
.pushStyle({ fontSize: 24, fontFamilies: ["Roboto"], color: Skia.Color("black") })
.addText("Hello ")
.pushStyle({ fontSize: 24, fontFamilies: ["Roboto"], fontStyle: { weight: 700 } })
.addText("Skia")
.pop()
.build();
para.layout(PARAGRAPH_WIDTH);
return (
<Canvas style={{ width: PARAGRAPH_WIDTH, height: para.getHeight() }}>
<Paragraph paragraph={para} x={0} y={0} width={PARAGRAPH_WIDTH} />
</Canvas>
);
}
Why good: mixed font weights in a single text block, automatic line breaking, measurable dimensions with getHeight() and getLongestLine()
See examples/text-and-media.md for font loading, text styles, and effects on paragraphs.
Pattern 8: Atlas Batch Rendering
Atlas draws thousands of sprites in a single draw call using one texture. Each sprite gets an individual RSXform (rotation + scale + translation). Ideal for tile maps, particle systems, and sprite animations.
import { Canvas, Atlas, useImage, Skia, rect } from "@shopify/react-native-skia";
const SPRITE_SIZE = 32;
const GRID_COLS = 10;
const GRID_ROWS = 10;
export function TileMap() {
const texture = useImage(require("./spritesheet.png"));
if (!texture) return null;
const sprites = Array.from({ length: GRID_COLS * GRID_ROWS }, () =>
rect(0, 0, SPRITE_SIZE, SPRITE_SIZE)
);
const transforms = Array.from({ length: GRID_COLS * GRID_ROWS }, (_, i) => {
const col = i % GRID_COLS;
const row = Math.floor(i / GRID_COLS);
return Skia.RSXform(1, 0, col * SPRITE_SIZE, row * SPRITE_SIZE);
});
return (
<Canvas style={{ width: GRID_COLS * SPRITE_SIZE, height: GRID_ROWS * SPRITE_SIZE }}>
<Atlas image={texture} sprites={sprites} transforms={transforms} />
</Canvas>
);
}
Why good: single draw call for 100 sprites, RSXform encodes scale+rotation+translation efficiently, transforms can be animated via useRSXformBuffer worklets at near-zero cost
See examples/animations.md for animated Atlas with useRSXformBuffer.
<decision_framework>
Decision Framework
When to Use Skia vs Native Views
Does the feature need custom drawing (paths, gradients, blur, shaders)?
├─ YES → Skia Canvas
└─ NO → Does it need high-performance batch rendering (100+ similar elements)?
├─ YES → Skia Atlas
└─ NO → Does it need rich text with mixed fonts/decorations?
├─ YES → Skia Paragraph (or native Text if simple)
└─ NO → Standard React Native views
Declarative vs Imperative API
Is the shape/path static or defined at build time?
├─ YES → Declarative JSX (<Circle />, <Path path="..." />)
└─ NO → Is the shape computed dynamically per frame?
├─ YES → Imperative (Skia.Path.Make()) inside worklets or useDerivedValue
└─ NO → Is the shape created once based on data?
├─ YES → Imperative, created outside render, passed as prop
└─ NO → Declarative with animated shared value props
Choosing the Right Text API
Need simple single-style text?
├─ YES → Skia <Text> component (single font, single style)
└─ NO → Need mixed fonts, weights, or line breaking?
├─ YES → Paragraph API (ParagraphBuilder)
└─ NO → Need text on a path?
├─ YES → <TextPath> component
└─ NO → <Text> with Glyphs for advanced positioning
</decision_framework>
<red_flags>
RED FLAGS
High Priority Issues:
- Using
createAnimatedComponentoruseAnimatedPropswith Skia components -- unnecessary; pass shared values directly as props - Using Reanimated's
interpolateColorfor Skia color animations -- produces wrong colors; useinterpolateColorsfrom@shopify/react-native-skia - Applying paint children (Shader, Blur) directly to Paragraph, Picture, or ImageSVG -- they ignore standard paint inheritance; use the
layerproperty - Using ScrollView with map() to render many Skia shapes -- use Atlas for batch rendering, or Picture for dynamic draw counts
- Performing heavy computations in Canvas children on JS thread -- offload to worklets or
useDerivedValueto keep animations on UI thread
Medium Priority Issues:
- Not setting
originon Group transforms and expecting center-based rotation -- default is top-left - RuntimeShader ignoring pixel density -- content appears blurry on high-DPI screens; wrap in a scale layer (scale by
PixelRatio.get(), then scale back by1/pd) - Creating new Path objects every render -- memoize or use
usePathValueto avoid garbage collection pressure - Hardcoding Canvas dimensions instead of using
onSizeshared value oruseCanvasSize-- breaks on different screen sizes - Missing
androidWarmup={true}for opaque canvases -- first frame renders white on Android
Gotchas & Edge Cases:
- Canvas uses its own React renderer -- React context from outside the Canvas is NOT available inside it
useCanvasSizereturns{ width: 0, height: 0 }on first render -- guard against zero dimensions- ImageSVG does not render
<text>elements, CSS styles, or<animate>-- preprocess SVGs with SVGO Skia.RuntimeEffect.Make()returns null if the shader has syntax errors -- always handle the null case- Atlas transforms are RSXform (rotation-scale-translation), not standard Transform2d -- use
Skia.RSXform()orSkia.RSXformFromRadians() - Path interpolation requires paths with the same number and types of commands -- mismatched paths crash
useImagereturns null while loading -- always guard rendering on image availability- Bundle size impact: +6MB iOS, +4MB Android, +2.9MB web (CanvasKit WASM)
- React Native >= 0.79 and React >= 19 required for current versions (v1.12.4 for RN <= 0.78)
- Canvas snapshot: use
makeImageSnapshotAsync()for images with textures,makeImageSnapshot()only for texture-free drawings useFontsreturns null while fonts load -- guard Paragraph rendering until fonts are ready
</red_flags>
<critical_reminders>
CRITICAL REMINDERS
All code must follow project conventions in CLAUDE.md
(You MUST pass Reanimated shared values directly as Skia component props -- do NOT use createAnimatedComponent or useAnimatedProps)
(You MUST use interpolateColors from @shopify/react-native-skia for color animations -- Reanimated's interpolateColor uses a different internal color format and produces wrong results)
(You MUST use the layer property to apply paint effects to Paragraph, Picture, and ImageSVG components -- they do not follow standard paint inheritance rules)
(You MUST remember that Group transform origin defaults to top-left, not center -- set origin prop explicitly for center-based rotations)
Failure to follow these rules will produce broken color animations, invisible paint effects, and incorrectly positioned rotations.
</critical_reminders>