Animations Skill
以下のようなタスクでこの Skill を使うこと。
- UI にアニメーションを追加する
- モーションデザインの品質をレビューする
- hover, popover, tooltip, dropdown, dialog, tab, segmented control, card, button, toast, accordion, scroll-linked animation, View Transitions などの動きを改善する
- アニメーションを削除すべきか判断する
- property, easing, duration, transform-origin などを選定する
prefers-reduced-motion を尊重すべきかどうかを判断する
@starting-style, transition-behavior, interpolate-size などの出現・退場アニメーション手法を選定する
@keyframes の命名規則・スコープ管理・API 的カスタムプロパティ設計を行う
基本原則
- アニメーションは目的ではなく手段である
- そのアニメーションが「機能的」か「装飾的」かを常に区別する
- 最良のアニメーションは「アニメーション無し」である場合もある
- UI の動きは速く、応答的に感じられるべきである。ユーザーが求めているのは「すぐ反応した」という知覚速度である
- 高頻度で操作される UI では装飾的なアニメーションを避ける
- イージングはアニメーションにおいて最重要である。同じ duration でもイージング次第で体感速度は大きく変わる
- 視覚変化はトリガーとの因果関係が感じられるべきである
- 複数の視覚変化を同期させたい場合、個別の
transition では不十分なことがある。@keyframes animation、Web Animations API、あるいは CSS トランジションと transition-behavior の組み合わせなど、適切な手法を選ぶこと
- アニメーションはパフォーマンスに大きく影響する
- アクセシビリティのため、
prefers-reduced-motion を尊重しているユーザーにどう見せるかを常に考える
必須の評価順序
Step 1: そのアニメーションは必要か?
必ず次を確認する。
- この動きは何の問題を解決するのか
- 状態、因果関係、フィードバックを明確にしているか
- 機能的なのか、装飾的なのか
- ユーザーはどれくらい頻繁にこれを見るのか
- キーボード操作や高速操作の邪魔にならないか
判断基準
| 目的 |
判断 |
例 |
| 状態遷移の因果関係を示す |
必要 |
アコーディオン開閉、タブ切替 |
| ユーザーの注意を新出要素に誘導する |
必要 |
toast 通知、バリデーションエラー |
| 操作のフィードバックを返す |
必要 |
ボタンの :active、チェックボックスのチェック |
| 空間的連続性を保つ |
条件付き |
ページ遷移の View Transitions、ドリルダウン |
| ブランド個性の表現 |
低頻度 UI のみ許容 |
LP のヒーロー、初回訪問のオンボーディング |
| 単に見た目の華やかさ |
高頻度 UI では不要 |
ダッシュボードのカードがスライドして現れるなど |
強い理由がない場合は、削除を提案すること。
Step 2: どの実装形が適切か?
Step 2A: イージング・タイミングの選定
イージング
ease-in 系や linear 系は物理的に不自然で機械的な印象を与えがちである。linear は一定速度を表現したい連続運動(marquee やプログレスバー)にのみ適用する。
- 出現・退場では
ease-out 系を使用する
- すでに画面上にあるものの移動では
ease-in-out 系を使用する
- CSS 標準のイージングキーワード(
ease, ease-in-out など)はメリハリが弱い。プロジェクトにアニメーショントークンがある場合はそれを参照し、quint 系か expo 系のイージングを使用する
/* ease-out 系(出現・退場向き) */
--ease--out-quint: cubic-bezier(0.22, 1, 0.36, 1);
--ease--out-expo: cubic-bezier(0.16, 1, 0.3, 1);
/* ease-in-out 系(移動向き) */
--ease--in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);
--ease--in-out-expo: cubic-bezier(0.87, 0, 0.13, 1);
- 全てのプロパティに同一の
transition-duration と transition-timing-function を適用するのは雑である。プロパティごとにカーブを選び分けること
タイミング
- 例外: dialog, drawer, sheet など大型 UI の遷移は
300ms〜500ms を許容する
- 日常的に何十回も触る UI では、1 回あたり
100ms の差でも体感負荷が大きくなる
Step 2B: transform-origin と空間的整合性
transform-origin は常にトリガーとの位置関係を考慮して設定する
- Popover はトリガー要素を起点として拡大・縮小させる。デフォルトの
center は多くの場合に不適切である
- CSS Anchor Positioning を使用している場合は、Popover の配置方向に応じて
transform-origin を動的に切り替えることを検討する
scale: 0 からのスケールインは物理的に不自然である。「そこにあったものが少し離れた位置から立ち上がる」ほうが自然であるため、要素のサイズに応じた起点値を選ぶこと
| 要素種別 |
スケールイン起点 |
| tooltip, popover |
0.95〜0.98 |
| dropdown menu |
0.92〜0.96 |
| dialog, drawer, sheet |
0.85〜0.92 |
Step 2C: インタラクション種別ごとの指針
ホバー
- ホバーの視覚変化は
150ms〜200ms を目安とする
- タッチデバイスでは hover が存在しないため、
@media (any-hover: hover) で切り分けることを検討する
- カレントリンクなど
href が存在しないアンカーリンクでホバーが起こるのは避ける。必ず :any-link:hover のように定義すること
- 有効ではないボタンでホバーが起こるのは避ける。必ず
:enabled:hover のように定義すること
active(押下フィードバック)
- ボタン等の押下時に
scale: 0.96〜0.98 程度の短い縮小を挟むことで、押下されたことへの物理的なフィードバックを提示する
- duration は
100ms〜150ms を目安とする
- あくまで押下が伝わることが重要な UI でのみ適用する
focus / keyboard
- キーボード操作中に視覚的な移動やハイライトにアニメーションがあると、入力と表示がズレて感じられ、反応が遅く見える
- キーボード操作時のアニメーションは厳禁とする
:not(:focus-visible) 内で transition や animation を定義し、キーボードフォーカス時にはアニメーションが適用されないようにする
tooltip
- ホバーの誤作動を防ぐため、初回表示は
300ms〜500ms 遅延させる
- 連続操作時(Warm Up 中)は
0ms〜50ms で即時表示する
- 離脱後のクールダウンは
300ms〜500ms で、この間に別のトリガーに触れた場合は即時表示を継続する
出現・退場(display の切替を伴う場合)
display: none から表示状態へのアニメーションには @starting-style で初期スタイルを定義し、transition-behavior: allow-discrete で display や overlay をトランジション可能にする
- Popover API(
[popover])や <dialog> のアニメーションでは、この手法を第一候補とする
@starting-style は詳細度を生成しないことを留意する
display: none への transition は Firefox、@keyframes 内の display は Safari で動作しない。前者はプログレッシブ・エンハンスメントとして許容する
高さのアニメーション(accordion 等)
height: auto へのアニメーションには interpolate-size: allow-keywords の使用を検討する
- Safari、Firefox でも動作するようにオーダーを受けた場合には、CSS Grid の
grid-template-rows: 0fr / 1fr パターンにフォールバックする
- layout を起こすアニメーションであるため、パフォーマンスに注意するが、アコーディオンの用途では許容する
scroll-linked animation
- 実装の優先順位
- CSS scroll-driven animations(
animation-timeline, view-timeline 等)
IntersectionObserver による状態トグル + CSS transition/animation
- 本当に必要な場合に限って JS で毎フレーム同期
scrollTop を毎フレーム読んでアニメーションを駆動している実装には必ず警告すること
- Scroll-driven animations を使う判断基準
- スクロール位置に連動して連続的に変化する値がある場合 → CSS scroll-driven animations
- 要素がビューポートに入った/出たタイミングで 1 回だけ状態変化する場合 →
IntersectionObserver
- スクロール量に応じて複雑なロジック分岐が必要な場合 → JS(ただし
requestAnimationFrame で制御する)
IntersectionObserver による状態トグルで発火させる場合は、animation-fill-mode: both と animation-play-state: paused を組み合わせた初期非表示パターンを使う
.reveal {
animation-name: --fade-in, --translate-from;
animation-duration: 600ms;
animation-timing-function: var(--ease--out-quint);
animation-fill-mode: both;
animation-play-state: paused;
/* アクティブ時に発火 */
&[data-revealed="true"] {
animation-play-state: initial;
}
}
animation-fill-mode: both により、paused 状態でも keyframes の from フレームが適用される。これにより要素は keyframes 上の開始地点(例: opacity: 0, translate: 0 20px)で描画される
- アクティブ化(属性付与等)で
animation-play-state を initial(= running)に戻すだけでアニメーションが発火する
- この手法の利点は、初期の非表示状態のために
opacity: 0 等を別途記述する必要がないこと。アニメーションの開始地点がそのまま初期表示となるため、keyframes を単一の情報源(single source of truth)として扱える
@media (prefers-reduced-motion: reduce) では animation-play-state の制御ごと無効化するか、アニメーションをフェードのみに簡素化すること
View Transitions
- View Transitions を扱うときは次を分けて考えること
- スナップショットの視覚アニメーション(見た目の動き)
- 実際のレイアウト変化(DOM の差分)
- SPA では
document.startViewTransition() を、MPA では @view-transition at-rule を使用する
view-transition-name の命名は CSS カスタムプロパティの命名規則に準じる
Step 2D: @keyframes の命名と設計
@keyframes はグローバルスコープで動作するため、命名と管理にルールを設けること。
設計原則
- グローバル keyframes はユーティリティクラスのように扱う。1 keyframes = 1 プロパティの変化に限定し、
animation-name のカンマ区切りで組み合わせる
- グローバル keyframes は
from / to の 2 フレームで完結するものに限定する。中間フレームを含む keyframes はコンポーネントなどのローカルとして定義する
- 自明な
from や to は省略する。省略されたフレームには要素の現在値が使われるため、カスケーディングに沿った自然なアニメーションになる。値をハードコードすると、要素の現在値が想定と異なる場合にジャンプが発生する
- 条件によってアニメーションの起点・方向を変えたい場合は、API 的カスタムプロパティを keyframes 内に定義し、利用側から値を注入する
/* グローバル keyframes(base/keyframes.css に配置) */
@keyframes --scale-from {
from {
scale: var(--scale-from--x-value, 1) var(--scale-from--y-value, 1);
}
}
/* 利用側: カスタムプロパティで起点を注入 */
[popover] {
--scale-from--x-value: 0.96;
--scale-from--y-value: 0.96;
animation-name: --scale-from;
animation-duration: 200ms;
animation-timing-function: var(--ease--out-quint);
animation-fill-mode: both;
}
その他
animation-fill-mode を明示する場合は both を第一候補とすることを推奨する
Step 3: パフォーマンスは適切か?
基本的なレンダリングコスト判断
必ず次の観点で考えること。
- layout / paint / composite のどれを起こすか
- main thread と compositor thread のどちらが関与するか
プロパティの選定
| 優先度 |
手法 |
備考 |
| 高 |
compositor で処理される transform, opacity, 一部の filter, 一部の clip-path, CSS scroll-driven animations |
GPU 合成のみで完結 |
| 中 |
JS から transform / opacity を更新する実装、FLIP パターン |
main thread が関与するが layout/paint は最小 |
| 低 |
paint を起こすプロパティ(background-color, box-shadow 等) |
描画コストは面積に依存。ホバーなどでは許容する |
| 避ける |
layout を起こすプロパティ(width, height, padding, inset 等) |
accordion 等やむを得ない場合を除く |
| リスク大 |
大きい半径の blur() / backdrop-filter、巨大な composited layer、グローバル CSS カスタムプロパティの毎フレーム更新、scrollTop を毎フレーム読む scroll-linked animation |
|
filter / backdrop-filter の注意
filter はハードウェアアクセラレーションされる場合があっても、コストが低いわけではない
backdrop-filter は filter よりもさらに高コストになり得る。背面の描画面積全体を再描画するためである
- 以下のケースは逆効果になり得るので避けること
- 大きい blur 半径
- 広い描画面積(
backdrop-filter は特に注意)
- アニメーション中のレイヤー
- 複数の重なったエフェクト
カスタムプロパティの更新
- グローバルに適用されるカスタムプロパティを毎フレーム更新してはいけない
- 可能な限りローカルスコープのカスタムプロパティを更新する
- 継承が行われると連鎖的に計算が走るため、
@property で inherits: false を設定して継承をオフにする
- DOM 全体が必要としていない値を継承させないこと
will-change の扱い
will-change はアニメーション開始直前に付与し、完了後に除去するのが原則である
- 常時指定は逆効果になり得る(GPU メモリの浪費、合成レイヤーの不要な生成)
- 原則的に
will-change は使用せず、どうしても必要な場合のみ JS で取り除く処理を必須としつつ使用すること
contain の活用
- 必要に応じて
contain: content を指定してレイアウト・描画・スタイル計算の影響範囲を限定する。ただし機械的に指定するのではなく、以下の副作用には注意する必要がある
- 内包される
position: fixed はトップレイヤーを除いて absolute のように扱われる
- オーバーフローがクリップされる
- スタッキングコンテキストが生成される
transform の独立プロパティ
- 差分の明確化とコードの読みやすさを優先して独立プロパティ(
translate, rotate, scale)を使用する
- ただし、以下のケースでは
transform を使用してよい
- 独立プロパティの実行順序(translate → rotate → scale)では都合が悪い場合
skew() を使用する場合
- 独立プロパティではかえって読みにくくなる 3D 系の複合指定の場合
Step 4: アニメーションの中断・キャンセル
操作中にアニメーションが完了する前に次のインタラクションが発生した場合の挙動を考慮すること。
判断基準
| 状況 |
推奨挙動 |
実装手法 |
| hover → 即 unhover |
中間状態から逆再生 |
CSS transition |
| dialog 開く → 即閉じる |
中間状態から退場アニメーション開始 |
@starting-style + transition なら自動。@keyframes の場合は animation-fill-mode と JS 制御が必要 |
| タブ切替の連打 |
即座に切り替え、アニメーションをスキップ |
animation: none の強制リセット、または View Transitions の skipTransition() |
| スクロール方向の急変 |
現在位置から新しい方向に即追従 |
CSS scroll-driven animations なら自動追従 |
原則
- CSS
transition は中間状態からの逆再生が自動で行われるため、中断が頻発する UI では transition を優先する
@keyframes animation は中断制御が難しいため、中断が想定される UI では避けるか、Web Animations API で cancel() / reverse() を使う
- 高速連打される UI(タブ、セグメンテッドコントロール等)では、アニメーションそのものを短くする(
100ms〜150ms)か、スキップする仕組みを入れる
Step 5: prefers-reduced-motion を尊重しているか?
段階的な対応方針
全てのアニメーションを一律に無効化するのではなく、段階的に対応すること。
| 対応レベル |
内容 |
対象例 |
| 無効化 |
@media (prefers-reduced-motion: no-preference) 内でのみ定義 |
パララックス、自動ループカルーセル、marquee、transform 系の scroll-linked animation |
| 簡素化 |
動きを crossfade やフェードのみに置き換え |
ページ遷移の View Transitions、大型 UI の出現・退場 |
| 短縮 |
duration を大幅に短縮(50ms 以下) |
tooltip/popover のフェードイン |
| 維持 |
変更不要 |
スピナー、プログレスバー、フォーカスリングの表示 |
「大きな変動」の判断基準
以下のいずれかに該当する場合は「大きな変動」とみなし、無効化または簡素化する。
- 画面の 1/3 以上を占める要素の移動
- 回転を伴う動き
- ズーム(
scale の変化量が 0.5 以上)
- スクロールに連動する
transform 系のアニメーション
- 視覚的に揺れる、振動する動き
注意
- 装飾的なアニメーションはフェードイン・フェードアウト以外は全て
@media (prefers-reduced-motion: no-preference) 内で定義すること
- 機能的なアニメーションであっても、「大きな変動」に該当する場合は簡素化を行うこと
Step 6: リスクの最終確認
全ての Step を通過した後、最終チェックとして次を確認する。
返答フォーマット
新規アニメーション提案時
- 判断: 必要 / 不要(判断マトリクスの根拠を示す)
- 分類: 機能的 / 装飾的
- 動き方: easing / duration / property / transform-origin
- 実装方針: CSS のみ(
transition or @keyframes or @starting-style)/ CSS + JS / Web Animations API / FLIP
- keyframes 設計(
@keyframes 使用時): スコープ(グローバル or コンポーネント固有)/ API 的カスタムプロパティの要否
- 中断時の挙動: 逆再生 / スキップ / 不要
- reduced-motion 対応: 無効化 / 簡素化 / 短縮 / 維持
- リスク: アクセシビリティ、繰り返し利用時の摩擦、視覚的不整合、パフォーマンス
既存コードの修正提案時
- 判断: 維持 / 修正 / 削除
- 理由: UX 上の目的と利用頻度
- 修正内容: 具体的な変更箇所とコード例
- リスク: 修正による副作用
性能レビュー時
- 分類: layout / paint / composite
- リスク: 低 / 中 / 高
- 理由: invalidation の経路と関与スレッド
- より安全な代替案:
transform / clip-path / observer / ローカル変数 / @property など
- ブラウザ注意点: Chrome 固有最適化、Safari / Firefox の差異
参考資料
1---2name: kf-g-css-tak-animations3description: UI animations necessity judgment, recommendations for motion design, implementation strategies, and performance/accessibility reviews. Targets include hover, active, focus, popover, tooltip, dropdown, dialog, tab, segmented control, card, button, toast, accordion, carousel, scroll-linked animation, View Transitions, and more.4---56# Animations Skill78以下のようなタスクでこの Skill を使うこと。910- UI にアニメーションを追加する11- モーションデザインの品質をレビューする12- hover, popover, tooltip, dropdown, dialog, tab, segmented control, card, button, toast, accordion, scroll-linked animation, View Transitions などの動きを改善する13- アニメーションを削除すべきか判断する14- property, easing, duration, transform-origin などを選定する15- `prefers-reduced-motion` を尊重すべきかどうかを判断する16- `@starting-style`, `transition-behavior`, `interpolate-size` などの出現・退場アニメーション手法を選定する17- `@keyframes` の命名規則・スコープ管理・API 的カスタムプロパティ設計を行う1819---2021## 基本原則22231. アニメーションは目的ではなく手段である241. そのアニメーションが「機能的」か「装飾的」かを常に区別する251. 最良のアニメーションは「アニメーション無し」である場合もある261. UI の動きは速く、応答的に感じられるべきである。ユーザーが求めているのは「すぐ反応した」という知覚速度である271. 高頻度で操作される UI では装飾的なアニメーションを避ける281. イージングはアニメーションにおいて最重要である。同じ duration でもイージング次第で体感速度は大きく変わる291. 視覚変化はトリガーとの因果関係が感じられるべきである301. 複数の視覚変化を同期させたい場合、個別の `transition` では不十分なことがある。`@keyframes` animation、Web Animations API、あるいは CSS トランジションと `transition-behavior` の組み合わせなど、適切な手法を選ぶこと311. アニメーションはパフォーマンスに大きく影響する321. アクセシビリティのため、`prefers-reduced-motion` を尊重しているユーザーにどう見せるかを常に考える3334## 必須の評価順序3536### Step 1: そのアニメーションは必要か?3738必ず次を確認する。3940- この動きは何の問題を解決するのか41- 状態、因果関係、フィードバックを明確にしているか42- 機能的なのか、装飾的なのか43- ユーザーはどれくらい頻繁にこれを見るのか44- キーボード操作や高速操作の邪魔にならないか4546#### 判断基準4748| 目的 | 判断 | 例 |49| ---------------------------------- | ------------------ | ---------------------------------------------- |50| 状態遷移の因果関係を示す | 必要 | アコーディオン開閉、タブ切替 |51| ユーザーの注意を新出要素に誘導する | 必要 | toast 通知、バリデーションエラー |52| 操作のフィードバックを返す | 必要 | ボタンの `:active`、チェックボックスのチェック |53| 空間的連続性を保つ | 条件付き | ページ遷移の View Transitions、ドリルダウン |54| ブランド個性の表現 | 低頻度 UI のみ許容 | LP のヒーロー、初回訪問のオンボーディング |55| 単に見た目の華やかさ | 高頻度 UI では不要 | ダッシュボードのカードがスライドして現れるなど |5657強い理由がない場合は、削除を提案すること。5859### Step 2: どの実装形が適切か?6061#### Step 2A: イージング・タイミングの選定6263**イージング**6465- `ease-in` 系や `linear` 系は物理的に不自然で機械的な印象を与えがちである。`linear` は一定速度を表現したい連続運動(marquee やプログレスバー)にのみ適用する。66- 出現・退場では `ease-out` 系を使用する67- すでに画面上にあるものの移動では `ease-in-out` 系を使用する68- CSS 標準のイージングキーワード(`ease`, `ease-in-out` など)はメリハリが弱い。プロジェクトにアニメーショントークンがある場合はそれを参照し、`quint` 系か `expo` 系のイージングを使用する69 - トークンが存在しない場合は以下を推奨する7071```css72/* ease-out 系(出現・退場向き) */73--ease--out-quint: cubic-bezier(0.22, 1, 0.36, 1);74--ease--out-expo: cubic-bezier(0.16, 1, 0.3, 1);7576/* ease-in-out 系(移動向き) */77--ease--in-out-quint: cubic-bezier(0.86, 0, 0.07, 1);78--ease--in-out-expo: cubic-bezier(0.87, 0, 0.13, 1);79```8081- 全てのプロパティに同一の `transition-duration` と `transition-timing-function` を適用するのは雑である。プロパティごとにカーブを選び分けること8283**タイミング**8485- 例外: dialog, drawer, sheet など大型 UI の遷移は `300ms〜500ms` を許容する86- 日常的に何十回も触る UI では、1 回あたり `100ms` の差でも体感負荷が大きくなる8788#### Step 2B: transform-origin と空間的整合性8990- `transform-origin` は常にトリガーとの位置関係を考慮して設定する91- Popover はトリガー要素を起点として拡大・縮小させる。デフォルトの `center` は多くの場合に不適切である92 - CSS Anchor Positioning を使用している場合は、Popover の配置方向に応じて `transform-origin` を動的に切り替えることを検討する93- `scale: 0` からのスケールインは物理的に不自然である。「そこにあったものが少し離れた位置から立ち上がる」ほうが自然であるため、要素のサイズに応じた起点値を選ぶこと9495| 要素種別 | スケールイン起点 |96| --------------------- | ---------------- |97| tooltip, popover | `0.95〜0.98` |98| dropdown menu | `0.92〜0.96` |99| dialog, drawer, sheet | `0.85〜0.92` |100101#### Step 2C: インタラクション種別ごとの指針102103**ホバー**104105- ホバーの視覚変化は `150ms〜200ms` を目安とする106- タッチデバイスでは hover が存在しないため、`@media (any-hover: hover)` で切り分けることを検討する107 - カレントリンクなど `href` が存在しないアンカーリンクでホバーが起こるのは避ける。必ず `:any-link:hover` のように定義すること108 - 有効ではないボタンでホバーが起こるのは避ける。必ず `:enabled:hover` のように定義すること109110**active(押下フィードバック)**111112- ボタン等の押下時に `scale: 0.96〜0.98` 程度の短い縮小を挟むことで、押下されたことへの物理的なフィードバックを提示する113- duration は `100ms〜150ms` を目安とする114- あくまで押下が伝わることが重要な UI でのみ適用する115116**focus / keyboard**117118- キーボード操作中に視覚的な移動やハイライトにアニメーションがあると、入力と表示がズレて感じられ、反応が遅く見える119- キーボード操作時のアニメーションは厳禁とする120- `:not(:focus-visible)` 内で `transition` や `animation` を定義し、キーボードフォーカス時にはアニメーションが適用されないようにする121122**tooltip**123124- ホバーの誤作動を防ぐため、初回表示は `300ms〜500ms` 遅延させる125- 連続操作時(Warm Up 中)は `0ms〜50ms` で即時表示する126- 離脱後のクールダウンは `300ms〜500ms` で、この間に別のトリガーに触れた場合は即時表示を継続する127128**出現・退場(display の切替を伴う場合)**129130- `display: none` から表示状態へのアニメーションには `@starting-style` で初期スタイルを定義し、`transition-behavior: allow-discrete` で `display` や `overlay` をトランジション可能にする131- Popover API(`[popover]`)や `<dialog>` のアニメーションでは、この手法を第一候補とする132- `@starting-style` は詳細度を生成しないことを留意する133- `display: none` への `transition` は Firefox、`@keyframes` 内の `display` は Safari で動作しない。前者はプログレッシブ・エンハンスメントとして許容する134135**高さのアニメーション(accordion 等)**136137- `height: auto` へのアニメーションには `interpolate-size: allow-keywords` の使用を検討する138- Safari、Firefox でも動作するようにオーダーを受けた場合には、CSS Grid の `grid-template-rows: 0fr / 1fr` パターンにフォールバックする139- layout を起こすアニメーションであるため、パフォーマンスに注意するが、アコーディオンの用途では許容する140141**scroll-linked animation**142143- 実装の優先順位144 1. CSS scroll-driven animations(`animation-timeline`, `view-timeline` 等)145 1. `IntersectionObserver` による状態トグル + CSS transition/animation146 1. 本当に必要な場合に限って JS で毎フレーム同期147- `scrollTop` を毎フレーム読んでアニメーションを駆動している実装には必ず警告すること148- Scroll-driven animations を使う判断基準149 - スクロール位置に連動して連続的に変化する値がある場合 → CSS scroll-driven animations150 - 要素がビューポートに入った/出たタイミングで 1 回だけ状態変化する場合 → `IntersectionObserver`151 - スクロール量に応じて複雑なロジック分岐が必要な場合 → JS(ただし `requestAnimationFrame` で制御する)152- `IntersectionObserver` による状態トグルで発火させる場合は、`animation-fill-mode: both` と `animation-play-state: paused` を組み合わせた初期非表示パターンを使う153154```css155.reveal {156 animation-name: --fade-in, --translate-from;157 animation-duration: 600ms;158 animation-timing-function: var(--ease--out-quint);159 animation-fill-mode: both;160 animation-play-state: paused;161162 /* アクティブ時に発火 */163 &[data-revealed="true"] {164 animation-play-state: initial;165 }166}167```168169- `animation-fill-mode: both` により、`paused` 状態でも keyframes の `from` フレームが適用される。これにより要素は keyframes 上の開始地点(例: `opacity: 0`, `translate: 0 20px`)で描画される170- アクティブ化(属性付与等)で `animation-play-state` を `initial`(= `running`)に戻すだけでアニメーションが発火する171- この手法の利点は、初期の非表示状態のために `opacity: 0` 等を別途記述する必要がないこと。アニメーションの開始地点がそのまま初期表示となるため、keyframes を単一の情報源(single source of truth)として扱える172- `@media (prefers-reduced-motion: reduce)` では `animation-play-state` の制御ごと無効化するか、アニメーションをフェードのみに簡素化すること173174**View Transitions**175176- View Transitions を扱うときは次を分けて考えること177 - スナップショットの視覚アニメーション(見た目の動き)178 - 実際のレイアウト変化(DOM の差分)179- SPA では `document.startViewTransition()` を、MPA では `@view-transition` at-rule を使用する180- `view-transition-name` の命名は CSS カスタムプロパティの命名規則に準じる181182#### Step 2D: `@keyframes` の命名と設計183184`@keyframes` はグローバルスコープで動作するため、命名と管理にルールを設けること。185186**設計原則**1871881. グローバル keyframes はユーティリティクラスのように扱う。1 keyframes = 1 プロパティの変化に限定し、`animation-name` のカンマ区切りで組み合わせる1891. グローバル keyframes は `from` / `to` の 2 フレームで完結するものに限定する。中間フレームを含む keyframes はコンポーネントなどのローカルとして定義する1901. 自明な `from` や `to` は省略する。省略されたフレームには要素の現在値が使われるため、カスケーディングに沿った自然なアニメーションになる。値をハードコードすると、要素の現在値が想定と異なる場合にジャンプが発生する1911. 条件によってアニメーションの起点・方向を変えたい場合は、API 的カスタムプロパティを keyframes 内に定義し、利用側から値を注入する192193```css194/* グローバル keyframes(base/keyframes.css に配置) */195@keyframes --scale-from {196 from {197 scale: var(--scale-from--x-value, 1) var(--scale-from--y-value, 1);198 }199}200201/* 利用側: カスタムプロパティで起点を注入 */202[popover] {203 --scale-from--x-value: 0.96;204 --scale-from--y-value: 0.96;205206 animation-name: --scale-from;207 animation-duration: 200ms;208 animation-timing-function: var(--ease--out-quint);209 animation-fill-mode: both;210}211```212213**その他**214215- `animation-fill-mode` を明示する場合は `both` を第一候補とすることを推奨する216217### Step 3: パフォーマンスは適切か?218219#### 基本的なレンダリングコスト判断220221必ず次の観点で考えること。222223- layout / paint / composite のどれを起こすか224- main thread と compositor thread のどちらが関与するか225226#### プロパティの選定227228| 優先度 | 手法 | 備考 |229| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |230| 高 | compositor で処理される `transform`, `opacity`, 一部の `filter`, 一部の `clip-path`, CSS scroll-driven animations | GPU 合成のみで完結 |231| 中 | JS から `transform` / `opacity` を更新する実装、FLIP パターン | main thread が関与するが layout/paint は最小 |232| 低 | paint を起こすプロパティ(`background-color`, `box-shadow` 等) | 描画コストは面積に依存。ホバーなどでは許容する |233| 避ける | layout を起こすプロパティ(`width`, `height`, `padding`, `inset` 等) | accordion 等やむを得ない場合を除く |234| リスク大 | 大きい半径の `blur()` / `backdrop-filter`、巨大な composited layer、グローバル CSS カスタムプロパティの毎フレーム更新、`scrollTop` を毎フレーム読む scroll-linked animation | |235236#### `filter` / `backdrop-filter` の注意237238- `filter` はハードウェアアクセラレーションされる場合があっても、コストが低いわけではない239- `backdrop-filter` は `filter` よりもさらに高コストになり得る。背面の描画面積全体を再描画するためである240- 以下のケースは逆効果になり得るので避けること241 - 大きい blur 半径242 - 広い描画面積(`backdrop-filter` は特に注意)243 - アニメーション中のレイヤー244 - 複数の重なったエフェクト245246#### カスタムプロパティの更新247248- グローバルに適用されるカスタムプロパティを毎フレーム更新してはいけない249- 可能な限りローカルスコープのカスタムプロパティを更新する250- 継承が行われると連鎖的に計算が走るため、`@property` で `inherits: false` を設定して継承をオフにする251- DOM 全体が必要としていない値を継承させないこと252253#### `will-change` の扱い254255- `will-change` はアニメーション開始直前に付与し、完了後に除去するのが原則である256- 常時指定は逆効果になり得る(GPU メモリの浪費、合成レイヤーの不要な生成)257- 原則的に`will-change` は使用せず、どうしても必要な場合のみ JS で取り除く処理を必須としつつ使用すること258259#### `contain` の活用260261- 必要に応じて `contain: content` を指定してレイアウト・描画・スタイル計算の影響範囲を限定する。ただし機械的に指定するのではなく、以下の副作用には注意する必要がある262 - 内包される `position: fixed` はトップレイヤーを除いて `absolute` のように扱われる263 - オーバーフローがクリップされる264 - スタッキングコンテキストが生成される265266#### `transform` の独立プロパティ267268- 差分の明確化とコードの読みやすさを優先して独立プロパティ(`translate`, `rotate`, `scale`)を使用する269- ただし、以下のケースでは `transform` を使用してよい270 - 独立プロパティの実行順序(translate → rotate → scale)では都合が悪い場合271 - `skew()` を使用する場合272 - 独立プロパティではかえって読みにくくなる 3D 系の複合指定の場合273274### Step 4: アニメーションの中断・キャンセル275276操作中にアニメーションが完了する前に次のインタラクションが発生した場合の挙動を考慮すること。277278#### 判断基準279280| 状況 | 推奨挙動 | 実装手法 |281| ---------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------- |282| hover → 即 unhover | 中間状態から逆再生 | CSS `transition` |283| dialog 開く → 即閉じる | 中間状態から退場アニメーション開始 | `@starting-style` + `transition` なら自動。`@keyframes` の場合は `animation-fill-mode` と JS 制御が必要 |284| タブ切替の連打 | 即座に切り替え、アニメーションをスキップ | `animation: none` の強制リセット、または View Transitions の `skipTransition()` |285| スクロール方向の急変 | 現在位置から新しい方向に即追従 | CSS scroll-driven animations なら自動追従 |286287#### 原則288289- CSS `transition` は中間状態からの逆再生が自動で行われるため、中断が頻発する UI では `transition` を優先する290- `@keyframes` animation は中断制御が難しいため、中断が想定される UI では避けるか、Web Animations API で `cancel()` / `reverse()` を使う291- 高速連打される UI(タブ、セグメンテッドコントロール等)では、アニメーションそのものを短くする(`100ms〜150ms`)か、スキップする仕組みを入れる292293### Step 5: `prefers-reduced-motion` を尊重しているか?294295#### 段階的な対応方針296297全てのアニメーションを一律に無効化するのではなく、段階的に対応すること。298299| 対応レベル | 内容 | 対象例 |300| ---------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------- |301| 無効化 | `@media (prefers-reduced-motion: no-preference)` 内でのみ定義 | パララックス、自動ループカルーセル、marquee、`transform` 系の scroll-linked animation |302| 簡素化 | 動きを crossfade やフェードのみに置き換え | ページ遷移の View Transitions、大型 UI の出現・退場 |303| 短縮 | duration を大幅に短縮(`50ms` 以下) | tooltip/popover のフェードイン |304| 維持 | 変更不要 | スピナー、プログレスバー、フォーカスリングの表示 |305306#### 「大きな変動」の判断基準307308以下のいずれかに該当する場合は「大きな変動」とみなし、無効化または簡素化する。309310- 画面の 1/3 以上を占める要素の移動311- 回転を伴う動き312- ズーム(`scale` の変化量が `0.5` 以上)313- スクロールに連動する `transform` 系のアニメーション314- 視覚的に揺れる、振動する動き315316#### 注意317318- 装飾的なアニメーションはフェードイン・フェードアウト以外は全て `@media (prefers-reduced-motion: no-preference)` 内で定義すること319- 機能的なアニメーションであっても、「大きな変動」に該当する場合は簡素化を行うこと320321### Step 6: リスクの最終確認322323全ての Step を通過した後、最終チェックとして次を確認する。324325- [ ] イージングが弱すぎないか(CSS 標準キーワードのまま放置していないか)326- [ ] `transform-origin` がトリガーと合っているか327- [ ] 物理的に不自然なアニメーションになっていないか(`scale: 0` からなど)328- [ ] キーボード操作にモーションを入れていないか329- [ ] 複数の視覚変化がズレていないか330- [ ] 初回のみ遅延し、その後は即時にすべき UI ではないか(tooltip 等)331- [ ] 中断・キャンセル時の挙動は考慮されているか332- [ ] パフォーマンス(layout/paint の最小化、`will-change` の適切な管理)333- [ ] `prefers-reduced-motion` を尊重しているユーザーにとって適切か334- [ ] scroll-linked animation で初期非表示が必要な場合、`animation-fill-mode: both` + `animation-play-state: paused` パターンを使い、keyframes 外に `opacity: 0` 等を二重定義していないか335- [ ] `@keyframes` のスコープと配置場所は適切か336337## 返答フォーマット338339### 新規アニメーション提案時3403411. **判断**: 必要 / 不要(判断マトリクスの根拠を示す)3421. **分類**: 機能的 / 装飾的3431. **動き方**: easing / duration / property / transform-origin3441. **実装方針**: CSS のみ(`transition` or `@keyframes` or `@starting-style`)/ CSS + JS / Web Animations API / FLIP3451. **keyframes 設計**(`@keyframes` 使用時): スコープ(グローバル or コンポーネント固有)/ API 的カスタムプロパティの要否3461. **中断時の挙動**: 逆再生 / スキップ / 不要3471. **reduced-motion 対応**: 無効化 / 簡素化 / 短縮 / 維持3481. **リスク**: アクセシビリティ、繰り返し利用時の摩擦、視覚的不整合、パフォーマンス349350### 既存コードの修正提案時3513521. **判断**: 維持 / 修正 / 削除3531. **理由**: UX 上の目的と利用頻度3541. **修正内容**: 具体的な変更箇所とコード例3551. **リスク**: 修正による副作用356357### 性能レビュー時3583591. **分類**: layout / paint / composite3601. **リスク**: 低 / 中 / 高3611. **理由**: invalidation の経路と関与スレッド3621. **より安全な代替案**: `transform` / `clip-path` / observer / ローカル変数 / `@property` など3631. **ブラウザ注意点**: Chrome 固有最適化、Safari / Firefox の差異364365## 参考資料366367- [TAK - Animations Skill](https://gist.github.com/tak-dcxi/b1870cfe73e3089a1e4aa7da19c3374a)