mcpField
W
web-animation-view-transitions

web-animation-view-transitions

github.com/agents-incデザイン・UI26101 回インストール2 日前に更新
リポジトリ

web-animation-view-transitions について

単一ページ内の更新やページ遷移にブラウザーの View Transitions を実装するための実践ガイドです。機能検出、共有要素、CSS アニメーション、モーション低減への対応を扱います。

主な機能

  1. 01同一ドキュメント内とドキュメント間の遷移
  2. 02共有要素の命名パターン
  3. 03CSS による遷移アニメーションのカスタマイズ
  4. 04モーション低減と非対応ブラウザーへのフォールバック
  5. 05遷移タイミングと Promise のガイド

活用シーン

  1. 01ルートやビューの変更へのアニメーション追加
  2. 02共有するヒーロー要素のページ間移動
  3. 03方向に応じたナビゲーション演出
  4. 04モーション低減設定への対応

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/agents-inc/skills --skill web-animation-view-transitions

インストール後、Claude Code で/web-animation-view-transitionsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

metadata.yaml
reference.md
examples/
core.md
shared-elements.md
spa.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.updateCallbackDoneDOM 更新コールバックが終了したとき
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 全体に適用されるキーワードなので、名前として使うことはできません

コメント