React Native Skiaを使ってみよう!
こんにちは!テラーノベルでiOS/Android/Webとフロントエンド周りを担当している @kazutoyoです!
2024/05/30に行われました、「React Native Meetup #16 ft. カンム」でLTをさせていただきました!
今回はそちらの発表内容を元にした記事となります。
React Native Skiaについて
React Native Skiaは、Flutterなどでも使われるSkiaエンジンをReact Nativeで利用するためのライブラリです。
SkiaはCanvasライクなAPIを提供します。
それによって、React Native SkiaはネイティブのAPIでは表現しづらいリッチな要素のレンダリングに向いています。
たとえば、次のようなグラフの表示や描画パフォーマンスが必要になるようなUIを作るときに活用できます。
React Native Skiaを試してみよう!
円を描画する
まずは次のような画面に円を描画してみます。
Canvasの中に、Circleコンポーネントを配置し、Groupでブレンドモードを指定することで、色の乗算を行います。
import { Canvas, Circle, Group } from "@shopify/react-native-skia";
import { Dimensions } from "react-native";
const HelloWorld = () => {
const width = Dimensions.get("window").width;
const height = Dimensions.get("window").height;
const r = width * 0.33;
return (
<Canvas style={{ width, height }}>
<Group blendMode="multiply">
<Circle cx={r} cy={r} r={r} color="cyan" />
<Circle cx={width - r} cy={r} r={r} color="magenta" />
<Circle cx={width / 2} cy={width - r} r={r} color="yellow" />
</Group>
</Canvas>
);
}
export default HelloWorld;
簡単ですね!
アニメーションさせてみる
次は先程作った円をアニメーションさせてみましょう!
次のように円がグルグルと回りながら拡大、縮小します。
AnimatedCircleコンポーネントの作成
React Native Reanimatedを使い、円の位置と拡大/縮小アニメーションを行うコンポーネントを作成します。
type AnimatedCircleProps {
color: string;
size: number;
centerX: number;
centerY: number;
initialAngle: number;
}
const AnimatedCircle = ({color, size, centerX, centerY, initialAngle}: AnimatedCircleProps) => {
const distance = size * 0.25;
const angle = useSharedValue(initialAngle);
const r = useSharedValue(size * 0.3);
const cx = useDerivedValue(
() => centerX + distance * Math.cos(angle.value)
);
const cy = useDerivedValue(
() => centerY + distance * Math.sin(angle.value)
);
useEffect(() => {
angle.value = withRepeat(
withTiming(angle.value + Math.PI * 6, { duration: 3000 }),
-1
);
}, [angle]);
useEffect(() => {
r.value = withRepeat(withTiming(size * 1.15, { duration: 1500 }), -1, true);
}, [r, size]);
return <Circle cx={cx} cy={cy} r={r} color={color} />
}
AnimatedCircleコンポーネントをCanvasに配置
先程のCircleコンポーネントの変わりに、AnimatedCircleコンポーネントを配置します。
const AnimatedCircles = () => {
const headerHeight = useHeaderHeight();
const size = Dimensions.get("window").width * 0.33;
const centerX = Dimensions.get("window").width / 2;
const centerY = Dimensions.get("window").height / 2 - headerHeight;
return (
<Canvas style={{ flex: 1 }}>
<Group blendMode="multiply">
<AnimatedCircle centerX={centerX} centerY={centerY} size={size} initialAngle={0} color="cyan" />
<AnimatedCircle centerX={centerX} centerY={centerY} size={size} initialAngle={(Math.PI * 2) / 3} color="magenta" />
<AnimatedCircle centerX={centerX} centerY={centerY} size={size} initialAngle={(Math.PI * 4) / 3} color="yellow" />
</Group>
</Canvas>
);
};
これによって次のようにアニメーションを描画することが出来ます!
テキストを描画する
Canvas上にテキストを描画し、ついでに塗りの色をアニメーションさせてみましょう!
テキストの描画 (1)
Textコンポーネントを使うことで単純なテキストを描画することが出来ます。
テキストの改行など、レイアウトが必要な場合はParagraph コンポーネントを使うこともできます。
import {Canvas, Text, useFont, matchFont} from "@shopify/react-native-skia";
const GradientText = () => {
const fontSize = 32;
const fontFamily = Platform.select({ ios: "Helvetica", default: "serif" });
const font = matchFont({
fontFamily: fontFamily,
fontSize: fontSize,
fontWeight: "bold",
});
return (
<Canvas style={{ flex: 1 }}>
<Text x={20} y={100} font={font} text="Hello React Native Skia!" />
</Canvas>
);
};
テキストの塗りのアニメーション
次のようにReact Natie ReanimatedのinterpolateColors関数などを使い、グラデーションの色を変化させます。
const startColors = ["rgba(34, 193, 195, 0.4)", "rgba(34,193,195,0.4)", "rgba(63,94,251,1)", "rgba(253,29,29,0.4)"];
const endColors = ["rgba(0,212,255,0.4)", "rgba(253,187,45,0.4)", "rgba(252,70,107,1)", "rgba(252,176,69,0.4)"];
const colorsIndex = useSharedValue(0);
// colorsIndexを3秒毎に変える
useEffect(() => {
colorsIndex.value = withRepeat(
withTiming(startColors.length - 1, {
duration: 3000,
}),
-1,
true
);
}, [colorsIndex]);
// colorIndexの値から色を変化させるアニメーション
const gradientColors = useDerivedValue(() => {
return [
interpolateColors(colorsIndex.value, [0, 1, 2, 3], startColors),
interpolateColors(colorsIndex.value, [0, 1, 2, 3], endColors),
];
});
テキストにLinearGradientを指定
TextとLinearGradientを指定して、Canvasに描画します。
return (
<Canvas style={{ flex: 1 }}>
<Fill color="#fff" />
<Text x={20} y={100} font={font} text="Hello React Native Skia!">
<LinearGradient
start={vec(0, 0)}
end={vec(width, fontSize)}
colors={gradientColors}
/>
</Text>
</Canvas>
);
これでテキストの表示と塗りのアニメーションが完成です!🥳
画像を描画する
次のように、画像の描画 + React Native Gesture Handler + Blur + Maskといったものをつくってみましょう!
React Native Gesture Handlerの設定
React Native Gesture Handler(RNGH)を使って、パンジェスチャーを設定します。
次のように Gesture.Pan()
の onChange
でoffsetX, offsetYを変更し、指を離したタイミングで0地点に戻します。
また、その移動した距離を使ってブラーの強さを0〜10の間で調整します。
const offsetX = useSharedValue<number>(0);
const offsetY = useSharedValue<number>(0);
const pan = Gesture.Pan()
.onChange((event) => {
offsetX.value = event.translationX;
offsetY.value = event.translationY;
})
.onFinalize(() => {
offsetX.value = withSpring(0);
offsetY.value = withSpring(0);
});
const imageX = useDerivedValue(() => {
return (width - size.width) / 2 + offsetX.value;
});
const imageY = useDerivedValue(() => {
return (height - size.height) / 2 + offsetY.value;
});
const blur = useDerivedValue(() => {
const offset = Math.max(Math.abs(offsetX.value), Math.abs(offsetY.value));
return Math.min((offset / 100), 1.0) * 10;
});
画像の読み込み
useImage hookを使って画像を読み込みます。
useImage
hookは require
でローカルの画像を読み込むか、 https://~
で指定してリモートの画像を読み込むことができます。
useImage
hookで読み込まれたリソースをImageコンポーネントに渡し表示できます。
const image = useImage(require("./assets/images/image.png"));
~~~
<GestureDetector gesture={pan}>
<Canvas style={{ flex: 1 }}>
<Fill color="#333" />
<Mask
mode='luminance'
mask={
<Circle cx={width / 2} cy={height / 2} r={width/2} color="white" />
}
>
<Image image={image} x={imageX} y={imageY} width={size.width} height={size.height}>
<Blur blur={blur} />
</Image>
</Mask>
</Canvas>
</GestureDetector>
以上で次のようなUIが実装できました!
まとめ
今回は簡単な表現のみでしたが、冒頭の例でも紹介ように、React Native Skiaを使うことでリッチでハイパフォーマンスなUIを作成することが出来ます。
React Native Skiaでどんな表現が出来るかは、CandillonさんのXやYouTubeチャンネルをチェックすることで、より詳しく知ることが出来るかと思います。
もちろん通常のReact NativeでUIを作るより、レイアウトや実装が大変な面もありますが、表現の幅は無限大になると思います。
画面の一部だけReact Native Skiaを使いたい!ということもできるので、必要なときにチャレンジしてみてはいかがでしょうか?
それでは良いReact Nativeライフを!
Discussion