クロスドキュメントビュー遷移の三大落とし穴と回避策

クロスドキュメントビュー遷移の三大落とし穴と回避策

クロスドキュメントビュー遷移の三大落とし穴と回避策

クロスドキュメントビュー遷移(Cross-Document View Transitions)は、MPA(マルチページアプリケーション)でありながらSPAのようなスムーズなページ遷移アニメーションを実現するブラウザAPIだ。ReactやAstroといったフレームワークは不要で、HTMLページ間のリンク遷移にブラウザが自動的にアニメーションを付与する。

しかし、この機能の導入にはいくつかの厄介な落とし穴が潜んでいる。CSS-Tricksの記事によれば、著者は実装に丸一日を費やし、何も動作しない状態からデバッグを繰り返したという。ネット上には古い情報や誤解を招くチュートリアルが溢れており、仕様自体も短期間で変更されている。

本記事では、実際の開発現場で遭遇する3つの主要な問題(非推奨metaタグ、4秒タイムアウト、画像の歪み)とその解決策を解説する。加えて、遷移ライフサイクルを制御する2つのイベントについても触れる。

非推奨となったmetaタグの罠

非推奨となったmetaタグの罠

多くの開発者が最初にハマるのが、古いチュートリアルに記載された<meta>タグによるオプトイン方式だ。この方式は既に非推奨であり、現在のブラウザでは完全に無視される。

非推奨(Before)
<meta name=”view-transition” content=”same-origin”>
ブラウザが沈黙し、何も動作しない
推奨(After)
@view-transition { navigation: auto; }
CSSでオプトイン、条件付き制御も可能

この比較が示すように、現在の正しい実装方法はCSSの@規則を使用することだ。Chrome 111でmetaタグが導入された後、Chrome 126前後でCSSベースの方式に置き換えられた。非推奨の警告はDevToolsに表示されず、古いコードは静かに動作しなくなる。

なぜCSS方式に移行したのか

metaタグ方式の最大の欠点は、ページ全体でオンかオフかの二択しかできなかったことだ。CSS方式ではメディアクエリや@supportsと組み合わせて、条件付きのオプトインが可能になる。

@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

@media (min-width: 768px) {
  @view-transition {
    navigation: auto;
  }
}

このアプローチにより、アニメーションに敏感なユーザーへの配慮や、モバイルデバイスでのパフォーマンス最適化が容易になった。CSSにオプトインが統合されたことで、既存のスタイル管理フローと一貫性を持って扱える点も大きい。

両方のページでオプトインが必須

もう一つの重要なポイントは、遷移を機能させるには遷移元と遷移先の両方のページで@view-transitionが宣言されている必要があることだ。片方だけでは何も起きない。これは意図的な設計で、404ページやログインリダイレクトなど遷移をスキップしたいページを柔軟に制御できる。

なお、navigation: autoはユーザーがリンクをクリックするかブラウザの戻るボタンを押した場合のみ発動する。window.location.hrefによるプログラム的な遷移や、クロスオリジンのリンク、POSTリクエストでは動作しない。この保守的な設計は、決済処理などの重要な操作に意図しないアニメーションが混入するのを防ぐためだ。

4秒タイムアウトが遷移を静かに殺す

4秒タイムアウトが遷移を静かに殺す

ビュー遷移の実装で最もデバッグが難しい問題が、ハードコードされた4秒のタイムアウトだ。新しいページが4秒以内にレンダリング可能な状態に達しないと、遷移アニメーションは何の通知もなくキャンセルされ、通常のページ読み込みのように切り替わる。

タイムアウト発生 4秒経過 ブラウザ 遷移を破棄 エラー表示なし
※TTFB(サーバー応答待ち2秒 + レンダリング2.5秒でアウト)

この問題が厄介なのは、ローカル開発環境ではまず発生しないことだ。devサーバーは80msで応答するため遷移は完璧に動作するが、本番環境でサーバーレス関数のコールドスタートやCDNキャッシュミスが発生すると、最初のクリックで遷移が無効化される。

pagerevealイベントでタイムアウトを捕捉する

タイムアウトの発生を検知するには、pagerevealイベントとviewTransition.finishedプロミスを使用する。以下のコードをページに組み込めば、遷移が失敗した際にコンソールで確認できる。

window.addEventListener("pagereveal", (event) => {
  if (!event.viewTransition) {
    console.log("ビュー遷移なし");
    return;
  }
  event.viewTransition.finished
    .then(() => console.log("遷移完了 ✅"))
    .catch((err) => {
      console.error("遷移中断", err.name, err.message);
    });
});

このリスナーを早期にセットアップしておけば、本番環境でのデバッグが格段に容易になる。pageswapイベントでも同様に遷移元ページ側でタイムアウトを捕捉可能だ。

実用的な対策

タイムアウト対策の基本はページの読み込み速度改善だが、より実践的なアプローチとしてrel="expect"属性の活用がある。

<link rel="expect" href="#hero" blocking="render">

これはブラウザに「#hero要素がDOMに存在するまでページをレンダリング可能と見なさない」と指示するものだ。一見するとパフォーマンスを悪化させるように思えるが、ビュー遷移においては重要なコンテンツが揃ってからスナップショットを取得するため、中途半端な状態での遷移を防げる。

タイムアウトのクロックはナビゲーション開始時からカウントされるため、サーバー応答時間(TTFB)も含まれる点に注意が必要だ。サーバーが2秒かけて応答し、さらに2.5秒かけてレンダリングする場合、個別には遅く感じなくても合計で4.5秒となりタイムアウトに引っかかる。

画像が歪む根本原因と解決策

画像が歪む根本原因と解決策

ビュー遷移の実装で視覚的に最も目立つ問題が、アスペクト比の異なる画像間の遷移で発生する歪みだ。サムネイルからヒーロー画像への遷移で、画像が引き伸ばされて見苦しくなる現象は多くの開発者が経験する。

CSS-Tricksの著者は、この問題の原因を特定するのにかなりの時間を費やしたという。根本的な原因は、ブラウザが遷移中に<img>要素そのものをアニメーションさせるのではなく、古い状態と新しい状態のスクリーンショット(ビットマップ)を取得し、それらを変形させることにある。

変形した状態(Before)
サムネイル
object-fit: cover → 無効化
ビットマップが引き伸ばされる
整形された状態(After)
::view-transition-old(hero-img),
::view-transition-new(hero-img) {
object-fit: cover;
overflow: hidden;
}

上記の図が示すように、解決策は疑似要素::view-transition-old::view-transition-newに対してobject-fit: coverを適用することだ。これにより、スナップショット画像がアスペクト比を維持したまま切り抜かれるようになる。

疑似要素ツリーの構造を理解する

ビュー遷移が発生すると、ブラウザは内部的に次のような疑似要素ツリーを生成する。

::view-transition
└── ::view-transition-group(hero-img)
    ├── ::view-transition-old(hero-img)
    └── ::view-transition-new(hero-img)

::view-transition-groupが古い寸法から新しい寸法へアニメーションするコンテナとして機能し、その中のoldnewが実際のスナップショットを保持する。デフォルトではこれらの疑似要素にobject-fit: fillが適用されており、これが歪みの原因となる。

アスペクト比が大きく異なるケースでは、object-positionで切り抜き位置を調整することも有効だ。

::view-transition-old(hero-img) {
  object-fit: cover;
  object-position: center center;
}
::view-transition-new(hero-img) {
  object-fit: cover;
  object-position: center top;
}

このコードでは、新しいヒーロー画像の上部を優先的に表示しつつ、遷移中の歪みを防ぐことができる。CSS-Tricksの著者も指摘するように、object-fit: coverはほぼ全ての画像遷移で必要になる設定であり、デフォルトがfillであることは実用上の大きな障壁となっている。

pageswapとpagerevealによるライフサイクル制御

pageswapとpagerevealによるライフサイクル制御

クロスドキュメントビュー遷移では、遷移元と遷移先のページがJavaScriptで直接通信できないという制約がある。この問題を解決するのがpageswappagerevealの2つのイベントだ。

遷移元ページ pageswap発火 要素に名前を付与
event.activation.entry.url で遷移先を特定可能
遷移先ページ pagereveal発火 遷移元の情報を取得
navigation.activation.from.url で遷移元を特定可能

このイベントペアにより、開発者は遷移の両端で状態を制御できる。pageswapは遷移元ページがスナップショットされる直前に発火し、event.activation.entry.urlでユーザーがどこへ向かっているかを知ることができる。

イベントハンドラの実装パターン

これらのイベントを使用する際の重要なポイントは、必ずevent.viewTransitionの存在確認を行うことだ。pagerevealはビュー遷移がない場合も含め、全てのナビゲーションで発火する。

window.addEventListener("pagereveal", (event) => {
  if (!event.viewTransition) return;
  
  event.viewTransition.finished.then(() => {
    // 遷移完了後のクリーンアップ
  }).catch((err) => {
    // タイムアウト等のエラー処理
  });
});

CSS-Tricksの記事では、商品一覧ページから商品詳細ページへの遷移において、pageswapでクリックされた商品カードだけにview-transition-nameを動的に付与するパターンが紹介されている。この動的な名前付けは、数十から数百の要素があるページでのスケーラビリティ問題を解決する重要な手法だ。

この記事のポイント

  • metaタグ方式は非推奨。CSSの@view-transition { navigation: auto; }を使用する
  • 4秒のタイムアウトはTTFBを含む総時間で判定され、pagerevealイベントで捕捉可能
  • 画像歪みは疑似要素::view-transition-old/newへのobject-fit: cover適用で解決
  • pageswappagerevealの2つのイベントが遷移全体のライフサイクルを制御する
海田 洋祐

・ 複数業界における17年間のデジタルビジネス開発経験 ・ ウェブサイト開発のためのHTML、PHP、CSS、Java等の実用的知識 ・ 15ヶ国語対応の多言語SaaSの開発経験 ・ 17年間にも及ぶ、Eコマース長期運営経験 ・ 幅広い業界でのSEO最適化の豊富な経験

メッセージを残す