Skottie(Lottieアニメーション)
SkiaのLottieアニメーションレンダラーSkottieの使い方。frameプロパティによる再生制御と、色・テキストなどをランタイムで書き換えるダイナミックプロパティを解説します。
Skottie(スコッティ)はSkiaに組み込まれたLottieアニメーションのレンダラーです。After Effectsで作ったアニメーションをBodymovin/Lottieプラグインで書き出したJSONを、React Native SkiaのCanvas上でそのまま再生できます。
デザイナーがAfter Effectsで作ったモーションをそのまま実機に持ち込めるのが最大の利点で、アプリ内のローディングアニメーションやオンボーディング演出などによく使われます。
Skottieコンポーネントの基本
Skia.Skottie.Make()にLottieのJSON文字列を渡すとアニメーションオブジェクトが作れます。これをSkottieコンポーネントのanimationpropに渡し、framepropで再生位置(フレーム番号)を指定します。
import { Canvas, Group, Skia, Skottie } from "@shopify/react-native-skia";
const animationJSON = require("./assets/animation.json");
const animation = Skia.Skottie.Make(JSON.stringify(animationJSON));
const SkottieExample = () => {
return (
<Canvas style={{ width: 400, height: 300 }}>
<Group transform={[{ scale: 0.5 }]}>
<Skottie animation={animation} frame={41} />
</Group>
</Canvas>
);
};
frameは単なる数値のpropなので、固定値を渡せば静止したコマ絵として、時間とともに変化する値を渡せばアニメーションとして描画されます。連続再生には前部の該当章で見た共有値(Shared Value)をそのまま使います。useClockが返す経過時間からフレーム番号を逆算するのが定番のパターンです。
import { Canvas, Group, Skia, Skottie, useClock } from "@shopify/react-native-skia";
import { useDerivedValue } from "react-native-reanimated";
const animation = Skia.Skottie.Make(JSON.stringify(require("./assets/animation.json")));
const AnimatedSkottieExample = () => {
const clock = useClock();
// 第2引数のdependenciesについては「Reanimatedとの連携」の章の「注意点」を参照。
const frame = useDerivedValue(() => {
const fps = animation.fps();
const totalFrames = animation.duration() * fps;
return Math.floor((clock.value / 1000) * fps) % totalFrames;
}, [clock]);
return (
<Canvas style={{ flex: 1 }}>
<Group transform={[{ scale: 0.5 }]}>
<Skottie animation={animation} frame={frame} />
</Group>
</Canvas>
);
};
animation.fps()とanimation.duration()(秒)から総フレーム数を求め、%で割った余りを取ることでループ再生になります。実機・ネイティブでは、frameがuseDerivedValueから来ている共有値である点も、前章までと同じくJSスレッドの再レンダリングを介さない仕組みです。
このデモのアニメーションは、円と回転する角丸四角形が3秒周期でループする自作のLottieデータです。スライダーで再生速度を変えられますが、frameの計算式に速度の係数を掛けているだけで、Lottieデータ自体は一切変更していません。
Web版のこのデモに限り、frameはuseDerivedValueの共有値ではなく通常のuseStateです。このプロジェクトのWeb版レンダラーには、共有値を<Skottie frame={...}>に直接渡しても再描画に反映されないという既知の制約があるため(詳しくは「Reanimatedとの連携」の章の「注意点」を参照)、requestAnimationFrameでReanimatedのclockをポーリングしてuseStateに反映する方式を使っています。実機・ネイティブでは上のコード例の通り、素直にuseDerivedValueを使ってください。
ダイナミックプロパティ: 実行時に中身を書き換える
Skottieの強みは、単に再生するだけでなく、色やテキストといったアニメーション内部のプロパティをJavaScript側から動的に書き換えられることです。デザインを差し替えずに、同じアニメーションを別の配色や別のテキストで使い回せます。
const animation = Skia.Skottie.Make(JSON.stringify(animationJSON));
// 色プロパティを取得して書き換える
const colorProps = animation.getColorProps();
if (colorProps.length > 0) {
animation.setColor(colorProps[0].key, Skia.Color("rgb(60, 120, 255)"));
}
// テキストプロパティを書き換える
animation.setText("hello!", "World", 164);
getColorProps()・getTextProps()・getOpacityProps()・getTransformProps()はそれぞれのプロパティの一覧を{ key, value }の配列で返します。keyはレイヤー名などアニメーション内部の識別子で、対応するsetColor()・setText()・setOpacity()・setTransform()に渡すことで値を書き換えられます。
スロット: 差し替え前提のプレースホルダー
After Effects側で「スロット」として名前を付けておいたプロパティは、getSlotInfo()で種類別にIDの一覧を取得し、setColorSlot()などで書き換えられます。デザイナーとエンジニアの分業がしやすくなる仕組みで、動的コンテンツに強いのが特徴です。
const slotInfo = animation.getSlotInfo();
// { colorSlotIDs: [...], imageSlotIDs: [...], scalarSlotIDs: [...], textSlotIDs: [...], vec2SlotIDs: [...] }
if (slotInfo.colorSlotIDs.length > 0) {
animation.setColorSlot(slotInfo.colorSlotIDs[0], Skia.Color("magenta"));
}
いずれのプロパティ書き換えも、変更が反映されるのは次にSkottieコンポーネントが再描画されたタイミングです。連続再生中であれば次のフレームで自然に反映されますし、静止画として使っている場合はReactの再レンダリングを1回挟む必要があります。
このデモのアニメーションは、円と回転する四角形が3秒周期でループする自作のLottieデータです。円の塗りはgetColorProps()で取得した通常の名前付きプロパティをsetColor()で書き換え、四角形の塗りはJSON側でsidを振ったスロットをgetSlotInfo()で取得しsetColorSlot()で書き換えています。同じ「色を変える」操作でも、プロパティ経由とスロット経由でAPIが異なる点に注目してください。
エフェクトの適用
Skottieコンポーネントは、GroupやRectのような通常のSkiaコンポーネントとは描画の仕組みが異なり(内部でSkiaのSkottieモジュールに描画を委譲しています)、BlurやColorMatrixのようなエフェクトを直接子要素として渡すことはできません。代わりにlayerpropを使います。これはImageSVGやParagraph、Pictureと同じ仕組みです。
<Group layer={<Paint><Blur blur={10} /></Paint>}>
<Skottie animation={animation} frame={41} />
</Group>
props・メソッドのリファレンス
| 名前 | 型 | 説明 |
|---|---|---|
| animation | SkSkottieAnimation |
Skia.Skottie.Make()で作ったアニメーションインスタンス |
| frame | number |
再生するフレーム番号(端数も有効。0.5なら1コマ目と2コマ目の中間) |
| メソッド | 説明 |
|---|---|
duration() |
アニメーションの長さ(秒) |
fps() |
フレームレート |
size() |
{ width, height } |
version() |
Lottieのバージョン文字列 |
getColorProps() / setColor(key, color) |
色プロパティの取得・書き換え |
getTextProps() / setText(key, text, size) |
テキストプロパティの取得・書き換え |
getOpacityProps() / setOpacity(key, opacity) |
不透明度プロパティの取得・書き換え |
getTransformProps() / setTransform(...) |
変形プロパティの取得・書き換え |
getSlotInfo() |
スロットIDの一覧を種類別に取得 |
setColorSlot / setScalarSlot / setVec2Slot / setTextSlot / setImageSlot |
スロット経由でのプロパティ書き換え |
アセットを含むアニメーション
Lottieアニメーションがフォントや画像などの外部アセットに依存している場合、Skia.Skottie.Make()の第2引数でアセット名とデータのマップを渡します。
const assets = {
NotoSerif: Skia.Data.fromBytes(fontBytes),
"img_0.png": Skia.Data.fromBytes(imageBytes),
};
const animation = Skia.Skottie.Make(JSON.stringify(animationJSON), assets);
注意点
Skottieはブラウザ上のCanvasKit(この本のデモが動いている環境そのもの)でも問題なく動きます。Node上でのオフスクリーンレンダリングにも対応しているので、Headlessレンダリングの章で見るサーバーサイドでのサムネイル生成などにも応用できます。
複雑なLottieアニメーション(レイヤー数が多い、マットやマスクを多用しているなど)は描画コストが上がるため、多数を同時に画面へ並べる場合はパフォーマンスに注意してください。
公式ドキュメントのSkottieも参照してください。