View Transition API によって、アニメーションをともなう遷移を、シンプルに実装することが可能になります。よく紹介される例としては、画面遷移のアニメーションが挙げられますが、コンポーネントのインタラクションのように、部分的な DOM の変更にも使うことができます。

View Transitions を理解するには、実際にコードに触れるのが近道ではありますが、この記事では、仕様をベースとした基本的な仕組みを説明します。

本記事で紹介する、View Transitions の機能は、W3C の仕様では「CSS View Transitions Module Level 1」もしくは、「CSS View Transitions Module Level 2」に属しています。

また、View Transitions は、同一ドキュメント内の遷移(same-document transitions)とクロスドキュメントの遷移(cross-document transitions)に分類できますが、この記事ではおもに前者にフォーカスします。

なお、2025 年 10 月 14 日にリリースされた Firefox 144 にて、View Transitions Module の Level 1(および Level 2 の一部の機能)がサポートされたことで1、同一ドキュメント内の遷移が、Baseline Newly available となりました2

ただし、仕様に関しては現時点では Editor’s Draft のため、今後、実装方法やブラウザの挙動が変わる可能性がある点には注意が必要です。

同一ドキュメント内の View Transition は、JavaScript の document.startViewTransition() メソッドを使用しますが、通常は、コールバック関数をあわせて指定し、その関数のなかで DOM を変更する処理を実行します。

// View Transition 開始
document.startViewTransition(() => updateCallback());

まずは、ブラウザのデフォルトの挙動を見てみましょう。

以下は、「View Transition 開始」ボタンを選択するたびに、上方向に 100px スクロールするデモです。このとき、スクロールは即座に切り替わるのではなく、クロスフェード効果をともなって変化していることがわかります。

Live Demo

スタイルは割愛していますが、以下に主要なコードを抜粋します。

ボタンを選択すると上方向にスクロールするデモ
<!-- HTML -->
<button type="button" data-vt="scroll-y">View Transition 開始</button>

<!-- JS -->
<script>
const scrollY = () => {
  // コールバック関数
  const updateCallback = () => {
    window.scrollBy({ top: -100 });
  };

  // フォールバック
  if (!document.startViewTransition) {
    updateCallback();
    return;
  }

  // View Transition 開始
  document.startViewTransition(() => updateCallback());
};

const btn = document.querySelector('[data-vt="scroll-y"]');
btn?.addEventListener('click', () => scrollY());
</script>

上記のコードでは、アニメーションを指定していませんが、デモではクロスフェード効果が付与されています。これは、View Transition を実行したときに、UA スタイルシートのアニメーションが適用されるためです。

また、フォールバックを指定することで、Progressive Enhancement になります。非対応の環境では遷移時のアニメーションは発生しませんが、この指定によってコールバック関数は実行されるため、最低限の機能は確保されます。

// フォールバック
if (!document.startViewTransition) {
  updateCallback();
  return;
}

// View Transition 開始
document.startViewTransition(() => updateCallback());

同じことを従来の方法で実現しようとすれば、要素をコピーして、絶対配置で上に重ねて、位置を調整して、アニメーションを実行して、アニメーションが終了したら古い要素を削除して、スクロール位置やフォーカス位置を調整して、などなど、非常に複雑処理が必要になるので、View Transitions によって、どれだけシンプルに実装できるかがわかると思います。

それでは、基本的なコードを確認したので、もう少し具体的に、View Transition の仕組みを見ていきましょう。

View Transition のライフサイクル

見出し「View Transition のライフサイクル」

View Transition の処理の流れを理解するには、仕様書の View Transition Lifecycle の図がわかりやすいのですが、基本的な流れとしては以下のとおりです。

  • 1 document.startViewTransition(updateCallback) を実行すると、ViewTransition インタフェースが返される
  • 2 DOM 変更前の状態(old)のスナップショットがキャプチャされる
  • 3 レンダリングが一時停止される
  • 4 コールバック関数(updateCallback)が呼び出され、DOM が変更される
  • 5 viewTransition.updateCallbackDone が履行される
  • 6 DOM 変更後の状態(new)のスナップショットがキャプチャされる
  • 7 擬似要素が生成される(::view-transition 以下)
  • 8 レンダリングの一時停止が解除され、擬似要素が表示される
  • 9 viewTransition.ready が履行される
  • 10 擬似要素のアニメーションが実行される
  • 11 擬似要素が削除される
  • 12 viewTransition.finished が履行される

View Transition でアニメーションしているのは実際の要素ではなく、擬似要素として生成される、DOM 変更前後(old、new)のスナップショットです。

ここで気をつける点としては、DOM 変更前(old)のスナップショットは静的なキャプチャですが、変更後(new)のスナップショットはライブキャプチャということです。遷移時に動き続ける要素(動画など)が含まれる場合には、この違いを理解することが重要です3

また、処理の流れを見ると、アニメーションが実行されるよりも前の段階でコールバック関数が呼び出され、DOM が変更されていることがわかります。そのため、View Transition が中断されたとしても、DOM の変更には支障がありません4

ViewTransition のプロパティである、updateCallbackDonereadyfinished は、それぞれのタイミングで何らかの処理を実行したいときに使用します。

たとえば finished は、View Transition が実行されている間だけ、特定の属性を付与するときなどに活用できます。

View Transition の期間内で data-vt 属性を有効にする例
const func = async () => {
  if (!document.startViewTransition) {
    await update();
    return;
  }

  const transition = document.startViewTransition(() => update());

  try {
    // `data-vt` 属性を有効化
    document.documentElement.setAttribute('data-vt', 'true');

    // View Transition の終了を待機
    await transition.finished;
  } finally {
    // `data-vt` 属性を無効化
    document.documentElement.removeAttribute('data-vt');
  }
};

View Transition の擬似要素

見出し「View Transition の擬似要素」

View Transition のライフサイクルで生成される擬似要素は、最上位のレイヤー(トップレイヤー)よりも上に追加され、アニメーションが終了すると削除されます。

デフォルトの擬似要素ツリーの構造は以下のとおりです。

::view-transition
└─ ::view-transition-group(root)
  └─ ::view-transition-image-pair(root)
      ├─ ::view-transition-old(root)
      └─ ::view-transition-new(root)
::view-transition
すべての View Transition の擬似要素を包含し、ドキュメントの最上位に配置される要素
::view-transition-group(root)
スナップショットのグループ要素
::view-transition-image-pair(root)
スナップショットを合成(mix-blend-mode)するときに、周囲の要素の影響を受けないように分離(isolation: isolate)するための要素
::view-transition-old(root)
DOM 変更前のスナップショット(静的なキャプチャ)
::view-transition-new(root)
DOM 変更後のスナップショット(ライブキャプチャ)

デフォルトの View Transition の名前は root のため、キャプチャの対象はドキュメント全体(<html> 要素)です。これは UA スタイルシートで以下のように定義されているためです。

UA スタイルシートの抜粋
:root {
  view-transition-name: root;
}

特定の要素をキャプチャしたい場合には、対象の要素をセレクタとして指定し、 view-transition-name プロパティを使用して名前を付与します。

view-transition-name プロパティの例
.nav {
  view-transition-name: menu;
}

これで、.nav セレクタに対してキャプチャする、menu という名前の View Transition 擬似要素が生成されます。ツリーの構造は以下のように変わります。

::view-transition
├─ ::view-transition-group(root)
│ └─ ::view-transition-image-pair(root)
│     ├─ ::view-transition-old(root)
│     └─ ::view-transition-new(root)
└─ ::view-transition-group(menu)
  └─ ::view-transition-image-pair(menu)
      ├─ ::view-transition-old(menu)
      └─ ::view-transition-new(menu)

このように、新たに作成されるグループは、1 つの View Transition の直下に置かれます。

なお、view-transition-name は、各要素に対してユニークである必要があります。以下のように、複数の要素に同じ名前が指定されていると、DOM の変更は実行されますが、すべての View Transition が無効になるので注意が必要です。

view-transition-name が重複している例
<!-- HTML -->
<h1 class="title">Card</h1>
<ul class="card-list">
  <li>foo</li>
  <li>bar</li>
  <li>baz</li>
</ul>

<!-- CSS -->
<style>
/* ❌ 複数の要素に同じ名前を指定 */
.card-list > li {
  view-transition-name: card;
}
/* それ以外のすべての View Transition も無効になる */
.title {
  view-transition-name: title;
}
</style>

この問題を回避するには、要素ごとにユニークな名前を指定するか、match-element キーワードを使用する必要がありますが、詳しくは次回の記事で取り上げる予定です。

UA スタイルシート(ブラウザのスタイルシート)の内容を把握することは、View Transition のスタイルをカスタマイズするときに意味を持ちます。

CSS View Transitions Module の Level 1 と Level 2 で微妙に内容が異なるのですが、ここでは後者のスタイルシートを参照します。

まずは、前述したように、ルート要素に対して、root という名前の View Transition グループが指定されています。

:root {
  view-transition-name: root;
}

::view-transition には、position: fixed が指定されているので、View Transition が実行されている間は、ビューポートに対して固定配置されます。

:root::view-transition {
  position: fixed;
  inset: 0;
}

::view-transition-group(*) の前半では、position: absolute で絶対配置されています。なお、スタイルシート内にサイズや位置の指定は見当たりませんが、これらのスタイルは条件に応じて動的に追加されるようです5

後半では、animation-durationanimation-fill-mode が指定されています。

パラメータにはグループ名ではなく全称セレクタ(*)が指定されているため、すべての View Transition が対象になります。

:root::view-transition-group(*) {
  position: absolute;
  top: 0;
  left: 0;

  animation-duration: 0.25s;
  animation-fill-mode: both;
}

::view-transition-old()::view-transition-new() には、レスポンシブイメージのスタイルが指定されていますが、実現したいアニメーションの内容によっては、この指定を調整する必要が出てくるかもしれません6

:root::view-transition-old(*),
:root::view-transition-new(*) {
  /* ... */
  inline-size: 100%;
  block-size: auto;
}

子孫要素に対しては、animation 関連のプロパティに inherit が指定されています。つまり、親要素である ::view-transition-group() に、これらのスタイルを指定することで、子孫要素にも継承されます。

:root::view-transition-image-pair(*),
:root::view-transition-old(*),
:root::view-transition-new(*) {
  animation-duration: inherit;
  animation-fill-mode: inherit;
  animation-delay: inherit;
  animation-timing-function: inherit;
  animation-iteration-count: inherit;
  animation-direction: inherit;
  animation-play-state: inherit;
}

Google Chrome で、これらの inherit の指定が反映されたのは、バージョン 140 からです7

さらに見ていくと、デフォルトで適用されるクロスフェード効果の @keyframes が用意されていますが、animation-name の指定は見当たりません。

これは、サイズや位置と同様に、::view-transition-old()::view-transition-new() に対して、動的にスタイルが追加されるようです。

/* Default cross-fade transition */
@keyframes -ua-view-transition-fade-out {
  to { opacity: 0; }
}
@keyframes -ua-view-transition-fade-in {
  from { opacity: 0; }
}

同様に、自然なクロスフェード効果を実現するための @keyframes が用意されていますが、こちらも、::view-transition-old()::view-transition-new() に対して、動的にスタイルが追加されるようです。

/* Keyframes for blending when there are 2 images */
@keyframes -ua-mix-blend-mode-plus-lighter {
  from { mix-blend-mode: plus-lighter }
  to { mix-blend-mode: plus-lighter }
}

View Transitions の検証には、Chromium 系ブラウザのデベロッパーツールを使用します。

まず、1 「Animations(アニメーション)」パネルを開いて、2 一時停止アイコンを選択します。その状態で、View Transition のアニメーションを実行すると、3 アニメーショングループが追加されます。

アニメーショングループを選択すると、その下に詳細情報が表示され、4 タイムラインを操作することで、アニメーションの変化を検証することができます。

デベロッパーツールの「Animations」パネルのスクリーンショット
UA スタイルシートに含まれる、-ua-view-transition-fade-out などのキーフレーム名が表示されていることが確認できる

また、この状態で「Elements(要素)」パネルを開くと、<html> 要素に ::view-transition 以下のツリーが追加されており、擬似要素を選択してスタイルを確認することができます。

デベロッパーツールの「Elements」パネルのスクリーンショット
View Transition の擬似要素ツリーが確認できる

View Transition のライフサイクルで説明したように、アニメーションが完了すると擬似要素が削除されてしまうので、このように一時停止する必要があるようです。

記事の前半で紹介した、ボタンを選択すると上方向にスクロールするデモを再掲しますので、デベロッパーツールを開いて上記の手順で、アニメーションを検証してみてください。

Live Demo

ユーザ設定に応じたアニメーションの調整

見出し「ユーザ設定に応じたアニメーションの調整」

ユーザが過度なアニメーションを避けるために、視差効果を減らす設定を有効にしているときには、どのように調整すればよいでしょうか。

まず、以下のように、CSS のメディアクエリで無効にする方法が考えられます。しかし、すべての View Transition に影響が及んでしまい、!important フラグを付与するので強力なため、個別に有効・無効を選択することが困難になります。

CSS で全体の View Transition を無効にする方法
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

影響範囲や副作用を考えると、以下のように、個別に view-transition-name を有効・無効に振り分けるのが無難です。

CSS で View Transition を振り分ける方法
/* 視差効果を減らす設定のときに UA スタイルシートの View Transition を無効 */
@media (prefers-reduced-motion: reduce) {
  :root {
    view-transition-name: none;
  }
}

/* ユーザ設定がされていないときに View Transition を有効 */
@media (prefers-reduced-motion: no-preference) {
  .nav {
    view-transition-name: menu;
  }
}

別案として、JS の matchMedia() メソッドを使用して振り分ける方法が考えられます。フォールバックの指定を応用して、メディアクエリが一致するときに、即座にコールバック関数を実行します。この方法であれば、JS の関数ごとに有効・無効を選択できます。

JS で View Transition を振り分ける方法
// 視差効果を減らすユーザ設定を判定
const isReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

// フォールバック or 視差効果を減らす
if (!document.startViewTransition || isReducedMotion) {
  updateCallback();
  return;
}

// View Transition 開始
document.startViewTransition(() => updateCallback());

ただし、クロスドキュメントの遷移(cross-document transitions)を、CSS の @view-transition 規則で実装する場合には、JS の方法では対応できません。

この記事では View Transitions の基本的な仕組みを説明しました。

View Transitions は新しい概念なので、その仕組みを理解するのは容易ではありませんが、DOM の変更と CSS アニメーションを分けて管理できることには大きな利点があります。

現時点では、仕様が Editor’s Draft であり、ブラウザのサポートも安定しているとはいえませんが、Progressive Enhancement が可能であるため。非対応の環境でアニメーションが発生しない点を許容できれば、採用していくこともできるでしょう。

次回は応用編として、いくつかの実例を取り上げながら、View Transition のアニメーションのカスタマイズ方法や、気をつけるポイントを説明していきます。

本記事の作成にあたり、以下のウェブページを参考にしました。

脚注

  1. Firefox 144.0 | Firefox Release Notes

  2. Same-document view transitions have become Baseline Newly available | web.dev

  3. 詳しくは「Same-document view transitions for single-page applications | Chrome for Developers」を参照してください。

  4. たとえば、アニメーションの途中でウィンドウをリサイズすると View Transition が中断されますが、DOM は変更後の状態になります。

  5. Setup transition pseudo-elements | CSS View Transitions Module Level 2

  6. View transitions: Handling aspect ratio changes | Jake Archibald

  7. View Transitions: Inherit more animation properties | Chrome Platform Status