web-animation-view-transitions について
単一ページ内の更新やページ遷移にブラウザーの View Transitions を実装するための実践ガイドです。機能検出、共有要素、CSS アニメーション、モーション低減への対応を扱います。
主な機能
- 01同一ドキュメント内とドキュメント間の遷移
- 02共有要素の命名パターン
- 03CSS による遷移アニメーションのカスタマイズ
- 04モーション低減と非対応ブラウザーへのフォールバック
- 05遷移タイミングと Promise のガイド
活用シーン
- 01ルートやビューの変更へのアニメーション追加
- 02共有するヒーロー要素のページ間移動
- 03方向に応じたナビゲーション演出
- 04モーション低減設定への対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ agents-inc/ skills --skill web-animation-view-transitionsインストール後、Claude Code で/web-animation-view-transitionsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
metadata.yamlreference.mdexamples/core.mdshared-elements.mdspa.md詳細
View Transitions API のパターン
クイックガイド: ブラウザーは古い状態をスクリーンショットに収め、新しい状態をライブのまま保持し、疑似要素のツリーとして両者の間をアニメーションさせます。同一ドキュメント内の変更は
document.startViewTransition()で制御し、ドキュメントをまたぐ変更は両方のページに記述した@view-transition { navigation: auto }で制御します。view-transition-nameを指定すると、要素をルートのスナップショットから切り離して単独で移動でき、アニメーション全体はスクリプトではなく CSS でカスタマイズします。
詳細なリソース:
- examples/core.md — 機能検出、状態遷移、3 つの Promise、スキップ、CSS によるオーバーライド
- examples/spa.md — テーマの表示、フォームのステップ、タブパネル、アコーディオン、リストの並べ替え、動きを抑える設定
- examples/shared-elements.md — ヒーローアニメーション、複数要素のカード、ドキュメントをまたぐペア、モーダル
- reference.md — 命名規則、疑似要素ツリー、ブラウザー対応状況、API テーブル
該当する方法
- スクリプトで DOM を更新するクライアント側ルーティングまたはページ内の状態変更 —
startViewTransition()に更新処理をコールバックとして渡すと、その前後の状態をキャプチャします。examples/spa.md を参照してください。 - サーバーでレンダリングされたページ間の遷移 — スクリプトは不要です。両方のドキュメントで
@view-transition { navigation: auto }を指定し、pageswapイベントとpagerevealイベントを通じて名前を設定します。examples/shared-elements.md を参照してください。 - 他の要素と一緒にクロスフェードさせず、2 つの状態間で移動させる要素がある場合 — 両方の状態で一致する
view-transition-nameが必要です。examples/shared-elements.md を参照してください。
<critical_requirements>
View Transitions のコードを書く前に
startViewTransition はサポート状況を確認してから呼び出し、更新処理はどちらの場合も実行してください。 この API が未実装の環境では、呼び出しが何もしないのではなく存在しないため、ガードなしで呼び出すと例外が発生し、DOM の変更もまったく実行されません。
参加する各要素には、ほかの表示要素が持たない名前を付けてください。 名前はスナップショットを識別します。表示要素が同じ名前を持つと、その要素の部分だけではなく、トランジション全体が中断されます。
動的に設定した view-transition-name は、transition.finished が解決したらクリアしてください。 要素に名前を残しておくと、次のトランジションでその名前を割り当てた際に競合します。そのため、最初の遷移ではなく、2 回目の遷移で問題が発生します。
カスタマイズするトランジションにはすべて、prefers-reduced-motion に応じた分岐を設けてください。 ページ全体のトランジションはビューポート全体を動かします。これはこの設定が対象とする種類の動きです。再生時間を短くするか、トランジションを省略して更新を直接適用してください。
</critical_requirements>
自動検出: startViewTransition, view-transition-name, view-transition-class, @view-transition, ::view-transition-old, ::view-transition-new, ::view-transition-group, ::view-transition-image-pair, :active-view-transition-type, pageswap, pagereveal, updateCallbackDone, skipTransition, match-element
適用対象:
- ビュー全体、またはその大部分が置き換わるほど大きな状態変更
- サーバーレンダリングされたサイトでのページ間遷移
- 2 つのビューにまたがって同じ要素が残るヒーローアニメーション
- 「進む」と「戻る」で逆方向の動きが必要な、方向を考慮した遷移
- 要素が変化するのではなく、移動する並べ替え
別の項目で扱う内容:
- 単一のコントロールに対するホバー、フォーカス、押下時のフィードバック — 置き換えられる要素がないため、キャプチャする前後のペアがありません
- ジェスチャーの途中で取り上げるスプリングのように、中断時にも速度が引き継がれる動き
- 納品物がスクラブ可能なタイムラインとなる、フレーム単位のタイムライン制作
ビューのトランジションとは、スクリーンショットとライブビューを互いにアニメーションさせるものです。すべてはここから導かれます。古い側は動かないピクセルなので、その中で動いていたものは停止します。要素に名前を付けると、その要素は独自のアニメーションを持つスナップショットのペアとして切り出されます。また、ツリーはスクリプトで駆動するアニメーションではなく、疑似要素のツリーなので、全体を CSS でスタイル設定します。
DOM の更新自体は通常どおりです。startViewTransition は、いずれにせよ行われる変更をラップするため、ブラウザーが未対応の場合のフォールバックは、単に変更を実行することです。
<decision_framework>
要素が属するスナップショット
Does an element exist on both sides and represent the same thing?
├─ YES -> give it one view-transition-name on both sides; it gets its own group and travels
└─ NO -> leave it in the root snapshot; it cross-fades with everything else around it
名前を付けた要素ごとに、グループ、画像ペア、2 つのスナップショットがツリーに追加されます。そのため、すべてに名前を付けると、得られるものよりもコストのほうが大きくなります。読み手が目で追う要素に名前を付けてください。
デフォルトのアニメーションか、カスタムアニメーションか
Cross-fade at a different duration -> set animation-duration on ::view-transition-old/new(root)
Slide, scale, wipe -> keyframes on the old and new pseudo-elements
Direction-dependent motion -> a transition type, selected with :active-view-transition-type()
Geometry computed at the moment of the transition (a reveal from a click point)
-> await transition.ready, then animate the pseudo-element
名前を設定する場所
ページ上で一意な要素(ヘッダー、ヒーロー、単一のパネルなど)には CSS が適しています。複数回表示される要素にはスクリプトが適しています。リストに対する静的なルールでは各行に同じ名前が付き、トランジションが壊れるためです。トランジションの前に要素ごとに名前を割り当て、完了後にクリアするか、サポートされている環境では view-transition-name: match-element を使用してください。
</decision_framework>
基本パターン
パターン 1:機能検出とフォールバック
const SUPPORTS_VIEW_TRANSITIONS =
typeof document !== "undefined" && "startViewTransition" in document;
function updateWithTransition(updateFn: () => void | Promise<void>): void {
if (!SUPPORTS_VIEW_TRANSITIONS) {
updateFn();
return;
}
document.startViewTransition(() => updateFn());
}
typeof document によるガードは、調べる対象となる document が存在しないサーバーレンダリング時に重要です。
コード全文:examples/core.md
パターン 2:同一ドキュメント内の遷移
コールバックが DOM を更新します。ブラウザーはコールバックを呼び出す前と、コールバックの解決後に DOM をキャプチャします。
const transition = document.startViewTransition(async () => {
await updateFn();
});
// Options form: classify the transition for CSS to select on
const transition = document.startViewTransition({
update: () => updateDOM(),
types: ["slide-forward"],
});
await transition.finished;
| Promise | 解決するタイミング |
|---|---|
transition.updateCallbackDone | DOM 更新コールバックが終了したとき |
transition.ready | 疑似要素ツリーが作成され、次にアニメーションが始まるとき |
transition.finished | アニメーションが終了し、新しいビューが表示されたとき |
async コールバックは解決するまでページをフリーズさせるため、その中で時間のかかる処理を行うと目に見える停滞が発生します。ネットワークの往復を待つのではなく、読み込み中の状態に更新してください。
完全なコード:examples/core.md
パターン 3:ドキュメント間の遷移
/* on both the source and the destination document */
@view-transition {
navigation: auto;
}
スクリプトもルーターの関与もなく、同一オリジン内のプッシュ、置換、トラバースによるナビゲーションをカバーします。片方だけがオプトインしても、遷移は発生しません。
完全なコード:examples/shared-elements.md
パターン 4:共有要素の遷移
変更の前後で名前を一致させると、クロスフェードではなく、1 つの要素が移動します。
.product-thumbnail {
view-transition-name: product-hero;
}
.product-image {
view-transition-name: product-hero;
}
::view-transition-group(product-hero) {
animation-duration: var(--hero-duration);
animation-timing-function: var(--hero-easing);
}
グループは位置とサイズをアニメーション化し、その中で画像のペアが 2 つのスナップショットをクロスフェードします。変更の前後でアスペクト比が異なる場合は、古い疑似要素と新しい疑似要素の object-fit によって、スナップショットがつぶれるのを防ぎます。
完全なコード:examples/shared-elements.md
パターン 5:カスタム CSS アニメーション
デフォルトのクロスフェードは、古い疑似要素と新しい疑似要素に適用される UA スタイルシートのアニメーションです。これを上書きするには通常の CSS を使います。
::view-transition-old(root) {
animation: slide-out-left var(--transition-duration) var(--transition-easing);
}
::view-transition-new(root) {
animation: slide-in-right var(--transition-duration) var(--transition-easing);
}
完全なコード:examples/core.md
パターン 6:方向に応じた遷移
遷移タイプは CSS で選択できるラベルです。そのため、前進と戻る場合で同じキーフレームを使い、適用するペアだけを切り替えられます。
html:active-view-transition-type(forwards) {
&::view-transition-old(content) {
animation-name: slide-out-left;
}
&::view-transition-new(content) {
animation-name: slide-in-right;
}
}
html:active-view-transition-type(backwards) {
&::view-transition-old(content) {
animation-name: slide-out-right;
}
&::view-transition-new(content) {
animation-name: slide-in-left;
}
}
document.startViewTransition({
update: () => navigateForward(),
types: ["forwards"],
});
transition.types は既存の遷移に設定できる変更可能なセットです。方向が更新の開始後にしかわからない場合に使います。
完全なコード:examples/spa.md
パターン 7:視差効果を減らす
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation-duration: 0.01ms !important;
}
}
const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
function shouldEnableTransitions(): boolean {
if (window.matchMedia(REDUCED_MOTION_QUERY).matches) return false;
return "startViewTransition" in document;
}
CSS を使う方法では、遷移の仕組みを維持したままアニメーションを縮小します。クロスフェードであればこれで十分です。スクリプトを使う方法では遷移自体をスキップします。ビューポート全体を使ったスライドやリビールの場合は、こちらが適切です。その中間に、3 つ目の方法があります。サイズと位置の変化を担う ::view-transition-group(*) のアニメーションをキャンセルし、古い疑似要素と新しい疑似要素のクロスフェードは残します。これにより、フィードバックを保ちながら移動をなくせます。
完全なコード:examples/spa.md
パターン 8:遷移時に計算するジオメトリ
クリックした瞬間にしかわからない値に応じてアニメーションを変える場合は、transition.ready を待ってから疑似要素を直接アニメーション化します。
const transition = document.startViewTransition(updateFn);
await transition.ready;
document.documentElement.animate(
{
clipPath: [`circle(0 at ${x}px ${y}px)`, `circle(${r}px at ${x}px ${y}px)`],
},
{
duration: REVEAL_DURATION_MS,
easing: REVEAL_EASING,
pseudoElement: "::view-transition-new(root)",
},
);
疑似要素には参照できる独自のノードがないため、pseudoElement オプションを使ってドキュメント要素をアニメーションの対象にします。
完全なコード:examples/spa.md
<red_flags>
注意すべき問題
実行時に問題が発生します:
- サポート確認をせずに
document.startViewTransition(...)を呼び出す — API が存在しない環境ではエラーが発生し、アニメーションだけでなく DOM の更新も失われます —"startViewTransition" in documentで分岐してください - 同じ
view-transition-nameを持つ表示中の要素が 2 つある — 正しく設定された部分も含め、トランジション全体が中断されます — 要素ごとにスクリプトから割り当てるか、match-elementを使ってください - リストセレクターに CSS で
view-transition-nameを設定する — すべての行が同じ名前を持つことになります — トランジションの前にスクリプトから設定し、後で解除してください transition.finishedの後も名前を割り当てたままにする — 次のトランジションで競合するため、原因が発生したタイミングより 1 回後のナビゲーションで問題が表面化します —finishedハンドラーで名前を解除してください- 2 つのドキュメントのうち一方にしか
@view-transitionがない — ドキュメントをまたぐトランジションでは両方のページで有効化する必要があります — 遷移先にもアットルールを追加してください transition.readyが解決する前に疑似要素をアニメーションする — ツリーがまだ存在しないため、アニメーションの対象がないまま何も起こりません — 先にプロミスをawaitしてくださいprefers-reduced-motionによる分岐がない — モーションを望まないユーザーにも、全画面のスライドが表示されます — メディアクエリーを追加するか、スクリプトでトランジションをスキップしてください<meta name="view-transition" content="same-origin">— 旧式で無視されます —@view-transitionアットルールを使ってください
予想外の動作:
- 古い側はスクリーンショットなので、動画、GIF、実行中のアニメーションは停止します。新しい側はライブ表示され、再生が続きます
- 名前はドキュメント全体で共有されるため、離れた位置にある無関係なセクションでも、同じ名前を使うと競合します
- トランジション中、ページは操作できなくなります。そのため、長いアニメーションはそのまま操作の遅延につながります — ナビゲーション経路上のアニメーションは 300ms 未満にしてください
- ドキュメントをまたぐトランジションには、同一オリジン間のナビゲーションが必要です
match-elementは Chrome 137 以降または Safari 18.4 以降が必要で、Firefox では利用できませんpagerevealハンドラーの登録が遅いとイベントを取り逃します —<head>に配置するか、スクリプトにblocking="render"を指定してくださいauto、inherit、none、unsetはカスタム識別子ではなく CSS 全体に適用されるキーワードなので、名前として使うことはできません