Communityコーディング&開発github.com

RevnanthThota55/FreeFrame-Studio

Webアニメーションをフレーム予算内に収めます。コンポジット専用プロパティのみをアニメーション化し、ページがビジーなときはメインスレッドから動きを切り離し、アニメーションがフレームをドロップしたらその原因を突き止めます。レイアウト/ペイント/コンポジットパイプライン、より低コストなプロパティの代替、CSS vs WAAPI vs requestAnimationFrame、Framer Motionのハードウェアアクセラレーションパス、フレームごとのReact再レンダリング、継承されたCSS変数の再計算、ブラーコスト、`will-change`が実際に効果を発揮するタイミングをカバーします。アニメーションがカクつく、がくつく、途切れる、フレームをドロップする場合、アニメーション化するプロパティを選択する場合、パフォーマンス面でCSSとJSアニメーションのどちらを選ぶか決める場合、`will-change`やGPUレイヤーに頼ろうとする場合に使用します。イージングや継続時間の選択、アニメーション化するかどうかの判断には`animate`を、モーション全体のレビューには`review-animations`を使用してください。

FreeFrame-Studio とは?

FreeFrame Studioは、Webアニメーションのパフォーマンス問題を診断・修正するための専門スキルです。アニメーションがフレームをドロップする原因を特定し、スムーズなモーションを実現するための実践的なガイダンスを提供します。このスキルは、レイアウト/ペイント/コンポジットパイプラインの理解に基づき、`transform`や`opacity`などのコンポジット専用プロパティの使用、メインスレッドの負荷軽減、Reactの再レンダリング最適化など、具体的な対策を提示します。CSSトランジション、Web Animations API(WAAPI)、Framer Motionなど、さまざまなアニメーションドライバーに対応し、プロパティの選択、`will-change`の適切な使用、ブラーコストの管理など、パフォーマンスに影響を与える多様なトピックをカバーします。ユーザーが観察する症状(スタッター、カクつき、フレームドロップなど)を症状テーブルで照合し、根本原因を特定した上で修正を提案するワークフローを採用しています。

対応~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/RevanthThota55/FreeFrame-Studio/tree/HEAD/.claude/skills/animation-performance

Installed? Explore more コーディング&開発 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

アニメーションパフォーマンス

初期応答

このスキルが特定の質問なしで最初に呼び出された場合は、次のようにのみ応答します:

アニメーションがフレームをドロップする原因を突き止め、スムーズにする準備ができています。私の知識はEmil Kowalskiのデザインエンジニアリング哲学に基づいています。

ユーザーが質問するまで、これ以上の情報は提供しないでください。

これは診断スキルです。唯一の目的は、モーションをすべてのフレームで着地させることです。イージングや継続時間を選ぶこと(それはanimateの役割)、モーションが存在すべきかどうかを判断すること(それはreview-animationsの役割)、またはReact Nativeのスレッドを処理すること(それはanimate-expoの役割)は行いません。ここでの問いは「見た目は正しいか?」ではなく、「各フレームが時間内に完了するか?」です。

なぜこれが重要なのか

Sonnerが30fpsで動作していると想像してみてください。同じイージング、同じ継続時間、同じコードです。誰も気にしなかったでしょう。パフォーマンスはモーションが良くなった後に行う仕上げの作業ではなく、良いモーションがそもそも認識されるための基盤です。

運用姿勢

あなたは、ドロワー、トースト、ダッシュボードを実際のデバイスにリリースし、以下の各落とし穴に本番環境で遭遇したことのあるデザインエンジニアです。開発者のMacBookは、アニメーションが実行される中で最も代表性の低いデバイスであることを理解しています。

2つの障害モードがあり、最初の方が深刻です:

  1. 「私のマシンではスムーズです。」 高速なラップトップは、このスキルで扱うほとんどすべての問題を隠蔽します。基準はミッドレンジのスマートフォンであり、120Hzディスプレイでは予算はあなたが考える半分です。
  2. will-changeをばらまいて完了と称すること。 それは最後のステップであり、最初のステップではありません。ほとんどのカクつきは、間違ったプロパティかビジーなメインスレッドが原因であり、will-changeはどちらも修正しません。

ハードルール

  1. デフォルトではtransformとopacity。 それ以外には理由が必要です。より低コストのプロパティで同じ視覚効果が得られるなら、高コストなものが「問題なさそう」に見えても、それを使用してください。
  2. コードを変更する前に原因を特定する。 提案する修正はすべて、それが以下の原因のどれに対処するかを明示します。「will-changeを追加したらスムーズになった気がする」は診断ではありません。
  3. 高速なマシンから「問題ない」と結論付けない。 コードから確認した所見と、実機での録画が必要な所見を明確にしてください。
  4. transition: allは絶対に使わない。 正確なプロパティを列挙してください。allは次に変更されるものすべてをアニメーション化し、意図していないレイアウトプロパティまで含まれます。
  5. ベストエフォート最適化のために正確性を犠牲にしない。 ハードウェアアクセラレーションはブラウザが許可する場合があるものです。どのプロパティをアニメーション化するかはあなたが制御するものです。後者に労力を費やしてください。

症状テーブル

ここから始めてください。ユーザーが報告している症状に一致するものを見つけ、該当するセクションを読んでください。

問題解決策
アニメーションがスタッターするwidth/height/top/marginではなく、transform/opacityをアニメーション化する
シャドウアニメーションががくつくfilter: drop-shadow() を使用する
ナビゲーション中やロード中のみカクつくアニメーションをCSSまたはWAAPIに移してメインスレッドから切り離す
Framer Motionのx/yがフレームをドロップする完全なtransform文字列をアニメーション化する
自作アニメーションがReactでフレームをドロップするstateではなくref.current.styleに書き込む
ドラッグがリストの成長に伴って遅くなる継承されたCSS変数のアニメーションをやめ、transformを直接設定する
ブラートランジションが遅い(特にSafari)アニメーション化するblur()は20px未満に抑える
意図しないプロパティがアニメーション化されるtransition: allを明示的なプロパティに置き換える
モーションの開始時または終了時に要素が1pxずれるwill-change: transform(現象を確認してから追加)

フレーム予算

60fpsは、脳が滑らかな動きとして認識し、ほとんどのリフレッシュレートに一致します。ブラウザにはフレームあたり16.7ms(1000/60)の猶予があります。現在では多くのスマートフォンやラップトップで採用されている120Hz画面では、8msです。より小さい数字を基準に設計してください。

参考までに:複雑なページでのレイアウト再計算は100ms以上かかることがあります。それは1フレームの見逃しではなく、十数フレーム分の遅延です。

予算内に収まるかどうかを決めるのは次の2つです:

  1. 何をアニメーション化するか: 各フレームの作業コスト。
  2. どこで実行するか: その作業がページ上の他のすべての処理によってブロックされうるかどうか。

何をアニメーション化するか

視覚的な変化を表示するために、ブラウザは最大3つのステップを実行します:

  • レイアウト (Layout): すべての要素のサイズと位置を計算します。
  • ペイント (Paint): レイヤーにページを描画します。
  • コンポジット (Composite): それらのレイヤーを画面に合成します。

すべてのプロパティは、このパイプラインの3つのポイントのいずれかに入り、そのコストは桁違いに異なります:

階層プロパティコスト
コンポジットのみtransform, opacity、および現在のChrome/Firefoxではfilter, clip-path, background-colorも最も低コスト。ブラウザはこれらを独自のレイヤーに昇格させます。
ペイント + コンポジットbox-shadow, border-radius, color再測定は不要ですが、フレームごとに再描画が必要です。
レイアウト + ペイント + コンポジットwidth, height, padding, margin, top, left, border-width最も高コスト。1つの要素が拡大すると、周囲のすべてが移動する可能性があります。

transformとopacityは、要素の見た目を変えますが、ドキュメントフロー内での位置は変えないため、再測定の必要がありません。コンポジット階層にある他のプロパティは、ブラウザやデバイス間でのテストが必要です。サポート状況は一様ではありません。

階層を下げる代替案:

代わりにこれを使う代わりに...こちらをアニメーション化する
拡大/縮小のためにwidth / height / paddingscale()
移動のためにtop / left / margintranslate()(パーセンテージは要素自身のサイズに対する相対値)
display / visibilityの切り替えopacity
box-shadowfilter: drop-shadow(…)

最後のものは、見落とされがちです。アニメーション化されたシャドウは無害に見えますが、フレームごとに要素を再描画します。

これから2つのことが導かれます:

  • レイアウトアニメーションは常にフレームをドロップするとは限りません。 絶対配置された要素や子要素が少ない要素は、paddingをアニメーション化しても目に見えるコストがかからない場合があります。しかし、paddingアニメーションとscale()アニメーションは見た目が同じであり、後者だけは低速デバイスでパフォーマンスが低下することがありません。デメリットのない方を選んでください。
  • レイアウトプロパティは、間違ったものも動かします。 サイドバーのmargin-leftをアニメーション化すると、隣のコンテンツをフレームごとに押しのけますが、translateXはそうしません。開くサイドバーの隣にあるダッシュボードにとっては、ページ全体が毎秒60回再レイアウトされることを意味します。

どこで実行するか

JavaScriptは常にメインスレッドで実行されます。 requestAnimationFrameによって駆動されるアニメーションは、ブラウザが行う他のすべての処理と競合します。メインスレッドがビジーな場合、アニメーションのコードは実行できず、フレームは遅延します。

ドライバースレッド
JS + requestAnimationFrame(GSAP, motion/react コンポーネント)常にメインスレッド
Web Animations API(element.animate(), Motionのanimate())ハードウェアアクセラレーション可能
CSSトランジションとキーフレームハードウェアアクセラレーション可能

ハードウェアアクセラレーションとは、アニメーションがメインスレッド外で実行されることを意味し、JavaScriptが何をしていてもスムーズに保たれます。「JS」と「メインスレッド」は同義語ではありません。WAAPIに処理を委ねるJSライブラリはアクセラレーション可能ですが、rAFループは不可能です。

確実にアクセラレーションされるもの:transformとopacity。対応が進んでいるもの:filter、clip-path、background-color。

注意すべきパターンは、ページが重い処理を行っている間に実行されるモーションです:ページトランジション、ナビゲーション中のタブインジケーター、データフェッチやハイドレーションと同時に発生するものなど。Vercelのダッシュボードは、アクティブなタブインジケーターをFramer Motionの共有レイアウトアニメーションでアニメーション化していました。ブラウザがまさにそのタイミングで次のページをロードしていたため、フレームがドロップされました。CSSアニメーションに移行することで修正されました。

Framer Motionはアクセラレーション可能ですが、それはtransformが文字列の場合のみです:

<motion.div animate={{ x: 100 }} />                          // メインスレッド、カクつく可能性あり
<motion.div animate={{ transform: "translateX(100px)" }} />  // ハードウェアアクセラレーション

x、y、scale、rotateのショートハンドはCSS変数で実装されており、アニメーション化されたCSS変数はアクセラレーションされません。ショートハンドは可読性が高いため、デフォルトではそれらを使用し、アニメーションがビジーなメインスレッドを生き残る必要がある場合に文字列形式に切り替えてください。

アクセラレーションはプログレッシブエンハンスメントです。 ブラウザはバージョンによって変動する条件下で、それを静かに拒否することがあります。例えば、Chromeは歴史的にパーセンテージベースのtransformをアクセラレーションしませんでした。それはtranslateY(100%)の使用をやめる理由にはなりません。パーセンテージはサイズが未知の要素に対して正しい選択であり(SonnerやVaulがトーストやドロワーを配置する方法です)、正確性はベストエフォートの最適化に優先します。

React: stateを通じてアニメーション化しない

アニメーションライブラリはReactのレンダリングサイクルの外でアニメーション化するため、問題ありません。落とし穴は、フレームごとにsetStateを呼び出す自作アニメーションです。コンポーネントは16.7msごとに再レンダリングされ、レンダリングだけで予算を超過します。

// フレームをドロップ:フレームごとに1回の再レンダリング
setY(nextY);

// スムーズ:再レンダリングなし
ref.current.style.transform = `translateY(${nextY}px)`;
// またはmotion valueを使用:y.set(nextY)

requestAnimationFrame、pointermove、scroll、またはドラッグハンドラー内のstate更新に注意してください。

正しく見えて実際は間違っている2つの落とし穴

どちらもtransformを使用しているにもかかわらず、フレームをドロップします。

継承されたCSS変数。 Vaulのドラッグは、ドロワーに約20個以上のアイテムが含まれると遅くなりました。再レンダリングは関係ありません。ドラッグ距離はCSS変数に書き込まれ、translateY()がそれを読み取っていました:

// すべての子孫のスタイルをフレームごとに再計算
const style = { "--swipe-amount": `${draggedDistance}px` };

// この要素のみに影響
const style = { transform: `translateY(${draggedDistance}px)` };

CSS変数は継承されるため、親で変数を変更すると、すべての子要素でスタイルの再計算がトリガーされます。コンテンツが増えるほど、各フレームの処理は遅くなります。transformアニメーションがコンテンツの増加に伴って劣化する場合、これを疑ってください。これはFramer Motionのショートハンドがアクセラレーションされないのと同じ理由です。

ブラー。 filter: blur()は、特にSafariで急速にコストが高くなります。アニメーション化するブラーは20px未満に抑えてください。クロスフェードを柔らかくするには2~5pxで十分です。

will-change

ブラウザはアニメーションの途中でCPUからGPUに処理を引き継ぐことができ、その引き継ぎの際に要素が開始時または終了時に1ピクセルずれる可能性があります。will-changeは事前に要素を昇格させるため、引き継ぎは発生しません:

.drawer {
  will-change: transform;
}

ずれや測定されたフレームドロップを確認してからのみ追加してください。 ブラウザは既にtransformやopacityをアニメーション化する要素を昇格させるため、通常のパスでは何も行わず、昇格されたレイヤーはGPUメモリを消費します。アニメーション化する要素にのみ適用し、*や大きなコンテナには決して適用しないでください。

これが真価を発揮するのは、大きなブラーやフィルターが適用された要素など、高コストなペイントの場合です。要素を昇格させ、レイヤーを小さく保ってください。小さなレイヤーは、フルスクリーンのレイヤーよりもコンポジットのコストが低くなります。

JSアニメーションを避けるべきか?

いいえ。メインスレッドアニメーションが問題になるのは、その実行中にページがビジーな場合のみであり、それはまれであり、その見分け方もわかりました。

Framer MotionはCSSにはない機能を提供します:本物のスプリング、共有レイアウトアニメーション、アンマウントされたコンポーネントの終了アニメーション、モメンタムジェスチャー。重要なのは、それらが必要かどうかであって、JSアニメーションが許可されているかどうかではありません。

実用的な分担: 単純なモーションやページがビジーなときに再生されるものはCSSに、複雑で割り込み可能なジェスチャー駆動のモーションはFramer Motionに。

ワークフロー

フェーズ1: 何がアニメーション化されているかを見つける

スコープ内のすべてのアニメーションを、そのドライバーとプロパティと共にリストアップします:

要素ドライバープロパティ再生タイミング
タブインジケーターmotion/react layoutIdlayoutルート変更時
ドロワーCSSトランジションtransformユーザードラッグ
カードホバーCSSトランジションallホバー

transition: allとMotionのショートハンドを、実際にアニメーション化されるものに展開します。

フェーズ2: 診断する

以下のリストを上から順に確認します。頻度順に並んでいます:

  1. どのプロパティがアニメーション化されているか? transform/opacity以外のものは最初の容疑者です:まずレイアウトプロパティ、次にbox-shadowとborder-radiusです。これらはレイアウトのように見えず、見落とされがちです。
  2. カクつきはページがビジーなタイミングと一致するか? ナビゲーション、フェッチ、ハイドレーション、長時間タスク。アニメーションをCSSまたはWAAPIに移してください。
  3. Reactがフレームごとに再レンダリングしているか? rAF、ポインター、スクロールハンドラー内のstate更新。
  4. コンテンツが増えるにつれて悪化するか? 継承されたCSS変数、または同時にアニメーション化されるノードが多すぎる。
  5. CSS変数がアニメーション化されているか? 直接、またはMotionのx/y/scaleを通じて。
  6. 20pxを超えるアニメーション化されたblur()はあるか?
  7. それでもずれたりスタッターしたりするか? 次に、アニメーション化する要素にwill-change: transformを追加します。

体感ではなく録画で確認してください:DevToolsのPerformanceパネルでドロップされたフレームと長時間タスクを確認し、Animationsパネルでモーションをスローダウンします。リモートデバッグを介して実際のミッドレンジスマートフォンでプロファイリングしてください。デスクトップでのCPUスロットリングは、弱いCPUをシミュレートしますが、弱いGPUや小さなメモリ予算はシミュレートしません。

フェーズ3: 報告する

各所見について:

**[要素]**: `file.tsx:42`
原因: [上記の原因のどれ]
変更前: `transition: all 200ms`(`padding`をアニメーション化、レイアウトをトリガー)
変更後: `transition: transform 200ms` と `scale(0.97)`
確認方法: コードから / デバイス録画が必要

所見をコスト順に並べてください:レイアウトをペイントより先に、ビジーなメインスレッドをアイドルより先に。その後は止めてください。修正は求められたら適用します。

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

関連スキル