Hooks
Below are animations hooks we provide when using React Native Skia with Reanimated. We also provide hooks for creating textures when integrating with Reanimated.
usePathInterpolation
This hook interpolates between different path values based on a progress value, providing smooth transitions between the provided paths.
Paths need to be interpolatable, meaning they must contain the same number and types of commands. If the paths have different commands or different numbers of commands, the interpolation may not behave as expected. Ensure that all paths in the outputRange
array are structured similarly for proper interpolation.
To interpolate two completely different paths, we found the flubber library to work well with Skia (see example).
tsx
importReact , {useEffect } from 'react';import {useSharedValue ,withTiming } from 'react-native-reanimated';import {Skia ,usePathInterpolation ,Canvas ,Path } from '@shopify/react-native-skia';constangryPath =Skia .Path .MakeFromSVGString ("M 16 25 C 32 27 43 28 49 28 C 54 28 62 28 73 26 C 66 54 60 70 55 74 C 51 77 40 75 27 55 C 25 50 21 40 27 55 Z")!;constnormalPath =Skia .Path .MakeFromSVGString ("M 21 31 C 31 32 39 32 43 33 C 67 35 80 36 81 38 C 84 42 74 57 66 60 C 62 61 46 59 32 50 C 24 44 20 37 21 31 Z")!;constgoodPath =Skia .Path .MakeFromSVGString ("M 21 45 C 21 37 24 29 29 25 C 34 20 38 18 45 18 C 58 18 69 30 69 45 C 69 60 58 72 45 72 C 32 72 21 60 21 45 Z")!;constDemo = () => {constprogress =useSharedValue (0);useEffect (() => {progress .value =withTiming (1, {duration : 1000 });}, []);constpath =usePathInterpolation (progress , [0, 0.5, 1], [angryPath ,normalPath ,goodPath ]);return (<Canvas style ={{flex : 1 }}><Path path ={path }style ="stroke"strokeWidth ={5}strokeCap ="round"strokeJoin ="round" /></Canvas >);};
tsx
importReact , {useEffect } from 'react';import {useSharedValue ,withTiming } from 'react-native-reanimated';import {Skia ,usePathInterpolation ,Canvas ,Path } from '@shopify/react-native-skia';constangryPath =Skia .Path .MakeFromSVGString ("M 16 25 C 32 27 43 28 49 28 C 54 28 62 28 73 26 C 66 54 60 70 55 74 C 51 77 40 75 27 55 C 25 50 21 40 27 55 Z")!;constnormalPath =Skia .Path .MakeFromSVGString ("M 21 31 C 31 32 39 32 43 33 C 67 35 80 36 81 38 C 84 42 74 57 66 60 C 62 61 46 59 32 50 C 24 44 20 37 21 31 Z")!;constgoodPath =Skia .Path .MakeFromSVGString ("M 21 45 C 21 37 24 29 29 25 C 34 20 38 18 45 18 C 58 18 69 30 69 45 C 69 60 58 72 45 72 C 32 72 21 60 21 45 Z")!;constDemo = () => {constprogress =useSharedValue (0);useEffect (() => {progress .value =withTiming (1, {duration : 1000 });}, []);constpath =usePathInterpolation (progress , [0, 0.5, 1], [angryPath ,normalPath ,goodPath ]);return (<Canvas style ={{flex : 1 }}><Path path ={path }style ="stroke"strokeWidth ={5}strokeCap ="round"strokeJoin ="round" /></Canvas >);};
usePathValue
This hooks offers an easy way to animate paths. Behind the scene, it make sure that everything is done as efficiently as possible. There is an optional second parameter available to set the default path value.
tsx
import {useSharedValue ,withSpring } from "react-native-reanimated";import {Gesture ,GestureDetector } from "react-native-gesture-handler";import {usePathValue ,Canvas ,Path ,processTransform3d ,Skia } from "@shopify/react-native-skia";constrrct =Skia .Path .Make ();rrct .addRRect (Skia .RRectXY (Skia .XYWHRect (0, 0, 100, 100), 10, 10));export constFrostedCard = () => {constrotateY =useSharedValue (0);constgesture =Gesture .Pan ().onChange ((event ) => {rotateY .value -=event .changeX / 300;});constclip =usePathValue ((path ) => {"worklet";path .transform (processTransform3d ([{translate : [50, 50] },{perspective : 300 },{rotateY :rotateY .value },{translate : [-50, -50] },]));},rrct );return (<GestureDetector gesture ={gesture }><Canvas style ={{flex : 1 }}><Path path ={clip } /></Canvas ></GestureDetector >);};
tsx
import {useSharedValue ,withSpring } from "react-native-reanimated";import {Gesture ,GestureDetector } from "react-native-gesture-handler";import {usePathValue ,Canvas ,Path ,processTransform3d ,Skia } from "@shopify/react-native-skia";constrrct =Skia .Path .Make ();rrct .addRRect (Skia .RRectXY (Skia .XYWHRect (0, 0, 100, 100), 10, 10));export constFrostedCard = () => {constrotateY =useSharedValue (0);constgesture =Gesture .Pan ().onChange ((event ) => {rotateY .value -=event .changeX / 300;});constclip =usePathValue ((path ) => {"worklet";path .transform (processTransform3d ([{translate : [50, 50] },{perspective : 300 },{rotateY :rotateY .value },{translate : [-50, -50] },]));},rrct );return (<GestureDetector gesture ={gesture }><Canvas style ={{flex : 1 }}><Path path ={clip } /></Canvas ></GestureDetector >);};
useClock
This hook returns a number indicating the time in milliseconds since the hook was activated.
tsx
import {Canvas ,useClock ,vec ,Circle } from "@shopify/react-native-skia";import {useDerivedValue } from "react-native-reanimated";export default functionApp () {constt =useClock ();consttransform =useDerivedValue (() => {constscale = (2 / (3 -Math .cos (2 *t .value ))) * 200;return [{translateX :scale *Math .cos (t .value ) },{translateY :scale * (Math .sin (2 *t .value ) / 2) },];});return (<Canvas style ={{flex : 1 }}><Circle c ={vec (0, 0)}r ={50}color ="cyan"transform ={transform } /></Canvas >);}
tsx
import {Canvas ,useClock ,vec ,Circle } from "@shopify/react-native-skia";import {useDerivedValue } from "react-native-reanimated";export default functionApp () {constt =useClock ();consttransform =useDerivedValue (() => {constscale = (2 / (3 -Math .cos (2 *t .value ))) * 200;return [{translateX :scale *Math .cos (t .value ) },{translateY :scale * (Math .sin (2 *t .value ) / 2) },];});return (<Canvas style ={{flex : 1 }}><Circle c ={vec (0, 0)}r ={50}color ="cyan"transform ={transform } /></Canvas >);}
Canvas Size
The Canvas element has an onSize
property that can receive a shared value, which will be updated whenever the canvas size changes.
tsx
import {useSharedValue } from "react-native-reanimated";import {Fill ,Canvas } from "@shopify/react-native-skia";constDemo = () => {// size will be updated as the canvas size changesconstsize =useSharedValue ({width : 0,height : 0 });return (<Canvas style ={{flex : 1 }}onSize ={size }><Fill color ="white" /></Canvas >);};
tsx
import {useSharedValue } from "react-native-reanimated";import {Fill ,Canvas } from "@shopify/react-native-skia";constDemo = () => {// size will be updated as the canvas size changesconstsize =useSharedValue ({width : 0,height : 0 });return (<Canvas style ={{flex : 1 }}onSize ={size }><Fill color ="white" /></Canvas >);};
useRectBuffer
Creates an array for rectangle to be animated. Can be used by any component that takes an array of rectangles as property, like the Atlas API.
tsx
import {useRectBuffer } from "@shopify/react-native-skia";constwidth = 256;constsize = 10;constrects = 100;// Important to not forget the worklet directiveconstrectBuffer =useRectBuffer (rects , (rect ,i ) => {"worklet";rect .setXYWH ((i *size ) %width ,Math .floor (i / (width /size )) *size ,size ,size );});
tsx
import {useRectBuffer } from "@shopify/react-native-skia";constwidth = 256;constsize = 10;constrects = 100;// Important to not forget the worklet directiveconstrectBuffer =useRectBuffer (rects , (rect ,i ) => {"worklet";rect .setXYWH ((i *size ) %width ,Math .floor (i / (width /size )) *size ,size ,size );});
useRSXformBuffer
Creates an array for rotate scale transforms to be animated. Can be used by any component that takes an array of rotate scale transforms as property, like the Atlas API.
tsx
import {useRSXformBuffer } from "@shopify/react-native-skia";import {useSharedValue } from "react-native-reanimated";constxforms = 100;constpos =useSharedValue ({x : 0,y : 0 });// Important to not forget the worklet directiveconsttransforms =useRSXformBuffer (xforms , (val ,i ) => {"worklet";constr =Math .atan2 (pos .value .y ,pos .value .x );val .set (Math .cos (r ),Math .sin (r ), 0, 0);});
tsx
import {useRSXformBuffer } from "@shopify/react-native-skia";import {useSharedValue } from "react-native-reanimated";constxforms = 100;constpos =useSharedValue ({x : 0,y : 0 });// Important to not forget the worklet directiveconsttransforms =useRSXformBuffer (xforms , (val ,i ) => {"worklet";constr =Math .atan2 (pos .value .y ,pos .value .x );val .set (Math .cos (r ),Math .sin (r ), 0, 0);});