CSS View Transitionsでページ遷移アニメーションを実装する方法【SPA/MPA両対応】

2026.04.03 09:00
2026.03.16 13:32
CSS View Transitionsでページ遷移アニメーションを実装する方法【SPA/MPA両対応】

ページ遷移のときに、パッと画面が切り替わるだけだと味気ないですよね。かといってJavaScriptでゴリゴリアニメーションを書くのも面倒……。そんな悩みを解決してくれるのが、View Transitions APIです。CSSとほんの少しのJavaScriptだけで、スムーズなページ遷移アニメーションが実装できます。

この記事では、View Transitions APIの基本から実践的なコード例まで、まるっとまとめてみました。SPAでもMPAでも使えるので、どんなプロジェクトでも活用できると思います。

サンプルページを見る(フェードクロス・カード拡大・スライドのデモ)

View Transitions APIとは

View Transitions APIは、画面の状態が変わるタイミングでアニメーションを付けるためのブラウザネイティブAPIです。従来はページ遷移アニメーションを実現しようとすると、ReactのFramer MotionやVueのTransitionコンポーネントなど、フレームワーク固有のソリューションに頼る必要がありました。

View Transitions APIの大きな特徴は以下の通りです。

  • SPA(Single Page Application): document.startViewTransition()でDOM更新時にアニメーション
  • MPA(Multi Page Application): @view-transition at-ruleでページナビゲーション時にアニメーション
  • フレームワーク非依存で、CSSとわずかなJavaScriptだけで動く
  • デフォルトでクロスフェードアニメーションが適用される

つまり、SPAでもMPAでも、同じAPIの考え方でページ遷移アニメーションが実装できるんですよね。

SPA版: document.startViewTransition()の基本

SPAの場合は、JavaScriptからdocument.startViewTransition()を呼び出してアニメーションを開始します。このメソッドにコールバック関数を渡すと、ブラウザが自動的に「古い状態のスナップショット」と「新しい状態」をキャプチャして、その間をアニメーションしてくれます。

// DOM更新をView Transitionでラップする
document.startViewTransition(() => {
  // ここでDOMを更新する
  updateContent();
});

// async/awaitパターン
document.startViewTransition(async () => {
  const data = await fetchNewContent();
  document.getElementById("main").innerHTML = data;
});

これだけで、ページ全体にデフォルトのクロスフェードアニメーションが適用されます。何もCSSを書かなくても、ふわっと切り替わる遷移が手に入ります。

startViewTransition()ViewTransitionオブジェクトを返します。これを使えば、アニメーションの完了を待つこともできます。

const transition = document.startViewTransition(() => {
  updateContent();
});

// アニメーション完了を待つ
await transition.finished;
console.log("遷移完了!");

MPA版: @view-transition at-rule

MPA(通常のページ遷移を伴うサイト)では、JavaScriptを書く必要すらないです。CSSの@view-transition at-ruleを使えば、ブラウザが自動的にページ遷移をアニメーションしてくれます。

重要なポイントは、遷移元と遷移先の両方のページでこのCSSを記述する必要があるということです。

/* 遷移元・遷移先の両方のページに記述 */
@view-transition {
  navigation: auto;
}

たったこれだけで、同一オリジン内のページ遷移時にクロスフェードアニメーションが適用されます。navigation: autoを指定すると、ブラウザが通常のナビゲーション(リンククリックなど)を自動的にView Transitionとして処理してくれます。

MPAのView Transitionは同一オリジン間のナビゲーションでのみ動作する点に注意です。外部サイトへの遷移にはアニメーションは適用されません。

view-transition-nameで要素を個別にアニメーション

デフォルトではページ全体が一つの塊としてアニメーションしますが、view-transition-nameプロパティを使うと、特定の要素を個別にアニメーションさせることができます。

/* ヘッダーは個別にアニメーション */
.header {
  view-transition-name: main-header;
}

/* メインコンテンツも個別に */
.main-content {
  view-transition-name: content;
}

/* カード要素にもユニークな名前を付ける */
.card-1 {
  view-transition-name: card-1;
}
.card-2 {
  view-transition-name: card-2;
}

ここで大事なルールが一つあります。view-transition-nameの値は、ページ内でユニークでなければならないんですよね。同じ名前を複数の要素に付けると、View Transitionはエラーになりアニメーションがスキップされてしまいます。

名前を付けた要素は、遷移前後のページで同じview-transition-nameを持つ要素同士がペアリングされ、その間を補間するアニメーションが生成されます。これが「共有要素トランジション」と呼ばれる仕組みです。

::view-transition-old / ::view-transition-new 疑似要素

アニメーションをカスタマイズするには、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-group(main-header)
│  └─ ::view-transition-image-pair(main-header)
│     ├─ ::view-transition-old(main-header)
│     └─ ::view-transition-new(main-header)
...

重要なのは以下の2つです。

  • ::view-transition-old(名前): 遷移前の状態の静的スナップショット(画像として保存される)
  • ::view-transition-new(名前): 遷移後の状態のライブ表示

これらの疑似要素にCSSアニメーションを指定することで、遷移のカスタマイズができます。

/* 古い状態はフェードアウト */
::view-transition-old(content) {
  animation: 0.3s ease-out fade-out;
}

/* 新しい状態はフェードイン */
::view-transition-new(content) {
  animation: 0.3s ease-in fade-in;
}

@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

実例1: フェードクロス遷移

まずは最もシンプルなフェードクロス遷移からです。古いコンテンツがフェードアウトしながら、新しいコンテンツがフェードインする定番パターンですね。

/* MPA: View Transitionを有効化 */
@view-transition {
  navigation: auto;
}

/* メインコンテンツに名前を付ける */
.main-content {
  view-transition-name: content;
}

/* フェードクロスのカスタマイズ */
::view-transition-old(content) {
  animation: 0.4s ease-out both fade-and-scale-out;
}

::view-transition-new(content) {
  animation: 0.4s ease-in both fade-and-scale-in;
}

@keyframes fade-and-scale-out {
  to {
    opacity: 0;
    transform: scale(0.95);
  }
}

@keyframes fade-and-scale-in {
  from {
    opacity: 0;
    transform: scale(1.05);
  }
}

ポイントはbothキーワードです。これをanimationに指定しておくと、アニメーションの開始前と終了後の両方でキーフレームの値が適用されるので、ちらつきを防げます。

実例2: カード→詳細ページの拡大遷移

一覧ページのカードをクリックすると、そのカードが拡大して詳細ページになる演出です。これはネイティブアプリでよく見るパターンで、View Transitions APIならめちゃくちゃ簡単に実装できました。

<!-- 一覧ページ -->
<div class="card-grid">
  <a href="/detail/1" class="card">
    <img src="/img/photo1.jpg" class="card-image"
         style="view-transition-name: hero-image-1">
    <h3 class="card-title"
        style="view-transition-name: hero-title-1">記事タイトル</h3>
  </a>
</div>
<!-- 詳細ページ -->
<article class="detail">
  <img src="/img/photo1.jpg" class="detail-image"
       style="view-transition-name: hero-image-1">
  <h1 class="detail-title"
      style="view-transition-name: hero-title-1">記事タイトル</h1>
  <p>本文がここに入ります...</p>
</article>
@view-transition {
  navigation: auto;
}

/* 画像の遷移: ゆったりとしたイージング */
::view-transition-group(hero-image-1) {
  animation-duration: 0.5s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* タイトルの遷移 */
::view-transition-group(hero-title-1) {
  animation-duration: 0.4s;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}

/* 残りのコンテンツはフェードイン */
::view-transition-new(root) {
  animation: 0.3s ease-in fade-in;
}

@keyframes fade-in {
  from { opacity: 0; }
}

同じview-transition-nameを持つ要素同士が自動的にペアリングされ、位置・サイズの変化がアニメーションされます。カードの小さな画像が詳細ページの大きなヒーロー画像に滑らかに変化する様子は、まるでネイティブアプリみたいです。

実例3: スライド遷移

ページが左右にスライドして切り替わるパターンです。「進む」と「戻る」で方向を変えると、より直感的なナビゲーション体験になります。

@view-transition {
  navigation: auto;
}

.main-content {
  view-transition-name: content;
}

/* 「進む」遷移: 右から左にスライド */
::view-transition-old(content) {
  animation: 0.35s ease-in-out both slide-out-left;
}

::view-transition-new(content) {
  animation: 0.35s ease-in-out both slide-in-right;
}

@keyframes slide-out-left {
  to {
    transform: translateX(-100%);
    opacity: 0;
  }
}

@keyframes slide-in-right {
  from {
    transform: translateX(100%);
    opacity: 0;
  }
}

SPAの場合は、JavaScriptで方向に応じてクラスを切り替えることで、「戻る」ときに逆方向のスライドを適用できます。

function navigate(url, direction = "forward") {
  // 方向に応じてクラスを設定
  document.documentElement.dataset.direction = direction;

  document.startViewTransition(async () => {
    const res = await fetch(url);
    const html = await res.text();
    document.querySelector(".main-content").innerHTML = html;
  });
}
/* 「戻る」ときは逆方向 */
[data-direction="back"] ::view-transition-old(content) {
  animation-name: slide-out-right;
}

[data-direction="back"] ::view-transition-new(content) {
  animation-name: slide-in-left;
}

@keyframes slide-out-right {
  to {
    transform: translateX(100%);
    opacity: 0;
  }
}

@keyframes slide-in-left {
  from {
    transform: translateX(-100%);
    opacity: 0;
  }
}

ブラウザ対応状況(2026年時点)

2026年3月時点でのブラウザサポート状況をまとめてみました。

SPA版(document.startViewTransition)

  • Chrome / Edge: 111以降 ✅
  • Safari: 18以降 ✅
  • Firefox: 133以降 ✅

MPA版(@view-transition / cross-document)

  • Chrome / Edge: 126以降 ✅
  • Safari: 18.2以降 ✅
  • Firefox: 151以降 ✅

2026年現在、主要ブラウザすべてでSPA・MPA両方のView Transitionsがサポートされています。プロダクション環境でも安心して使える状況ですね。

ただし、未対応ブラウザでもView Transitionは「単にアニメーションなしで通常通り動作する」だけなので、プログレッシブエンハンスメントとして導入しやすいです。機能検出を入れておけばさらに安全です。

// 機能検出
if (document.startViewTransition) {
  document.startViewTransition(() => updateContent());
} else {
  // フォールバック: アニメーションなしで更新
  updateContent();
}

prefers-reduced-motionへの配慮

アニメーションの実装で忘れてはならないのが、アクセシビリティへの配慮です。前庭障害(めまいや吐き気を感じやすい症状)を持つユーザーにとって、画面のアニメーションは身体的な不快感を引き起こす可能性があります。

prefers-reduced-motionメディアクエリを使って、アニメーションを軽減または無効化しておくのが大事です。

/* アニメーションを軽減したいユーザー向け */
@media (prefers-reduced-motion: reduce) {
  /* View Transitionのアニメーションを無効化 */
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0s !important;
  }
}

/* より丁寧なアプローチ: 完全に消すのではなく控えめにする */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation-duration: 0.01s !important;
  }
  
  /* スライドは無効にして、フェードだけ残す */
  ::view-transition-old(content) {
    animation: 0.15s ease-out fade-out;
  }
  ::view-transition-new(content) {
    animation: 0.15s ease-in fade-in;
  }
}

完全にアニメーションをオフにするか、控えめなフェードだけ残すかはプロジェクト次第ですが、少なくとも大きな動きを伴うスライドや拡大縮小は無効化しておいた方がいいですね。

まとめ

View Transitions APIは、Webのページ遷移体験を劇的に向上させるAPIでした。ポイントをおさらいします。

  • SPAではdocument.startViewTransition()でDOM更新をラップ
  • MPAでは@view-transition { navigation: auto; }を両ページに記述
  • view-transition-nameで個別要素のアニメーションを制御
  • ::view-transition-old()::view-transition-new()でカスタムアニメーションを指定
  • 2026年時点で主要ブラウザすべてがサポート済み
  • prefers-reduced-motionでアクセシビリティに配慮

サンプルページで実際の動きを確認する

特にMPA版はCSSだけで完結するので、既存のサイトにも後付けしやすいです。まずは@view-transition { navigation: auto; }を追加するところから試してみると良さそうです。たった2行のCSSで、サイトの印象がガラッと変わります。

今回は以上です!