【実務・中級編】 ::view-transition-old 擬似要素による遷移前状態 – CSS実践ガイド

やあ。今日も今日とてCSSの沼にどっぷり浸かっているかい?
フロントエンドの現場を渡り歩いていると、「ページ遷移をもっとリッチに、ネイティブアプリみたいに滑らかにできないか?」っていうクライアントやデザイナーからの無茶ぶり、いや熱い要望に何度も直面するよね。

これまで、ページ間の遷移アニメーションを実装しようとすると、SPAのルーターをいじり倒したり、複雑なJavaScriptのライブラリを導入してDOMの状態を必死に同期させたり……と、胃に穴が開きそうな実装コストを払ってきたはずだ。

だが、時代は変わった。現代のブラウザには View Transitions API がネイティブで備わっている。
そして、その中でも「遷移前のスナップショット」を華麗に料理するための鍵となるのが、今回深掘りする `::view-transition-old` 擬似要素 だ。

公式ドキュメントの解説を読むだけだと、「ふーん、古い画面をキャプチャしてアニメーションさせるのね」で終わりがちだが、実務でこれをガッツリ使いこなすには、ブラウザの裏側の挙動を知り、CSSの魔術を少しばかり理解しておく必要がある。
今日は、現場で即戦力となる知識と、明日から使える実践的なコードをご馳走しよう。心して聞いてくれ。

—

1. ブラウザの裏側で何が起きているのか?(`::view-transition-old` の正体)

まず、この擬似要素がCSSの通常のセレクタとどう違うのか、その根本を理解しておこう。

`document.startViewTransition()` が実行された瞬間、ブラウザは魔法のような処理を裏で行っている。
1. DOMのスナップショット撮影: 遷移「前」のドキュメントの現在の見た目を、一種のテクスチャ(画像のようなもの)としてキャプチャする。これが `::view-transition-old` の正体だ。
2. DOMの更新: コールバック関数が実行され、DOMツリーが新しい状態に書き換わる。
3. 新しいスナップショット撮影: 遷移「後」のドキュメントの見た目をキャプチャする。これが `::view-transition-new` だ。
4. アニメーションの再生: ブラウザは、旧画面のテクスチャ(`old`)から新画面のテクスチャ(`new`)へのクロスフェードなどのアニメーションを、裏で作った擬似要素ツリー上で合成して実行する。

つまり、`::view-transition-old` は、すでに「過去のもの」となった古いDOMの残骸をペタッと貼り付けたレイヤーだ。
このレイヤーに対して、標準のCSSアニメーションを当てて「どう消えていくか」を指図するのが、私たちの仕事になる。

—

2. デフォルトの挙動の罠と、シニアが必ずやる初期化

ブラウザのデフォルトのView Transitionは、実は「古い画面がフェードアウトしながら、新しい画面がフェードインする」というクロスフェードになっている。

これの何が問題か?
何もしないと、古い画面(`old`)が上層に居座ったまま、その下で新しい画面(`new`)がボワッと現れるため、体感的に「もっさりした遅延」を感じやすいんだ。特に重いページでは最悪だ。

プロの現場では、このデフォルトの挙動をまずリセットし、アニメーションのタイムラインやイージングを完全にコントロール下に置くことから始める。

実務で使える基本のCSS設計

まずは、すべてのView Transitionの基本骨格となるスタイルを見てほしい。コピペしてプロジェクトのグローバルCSSに放り込んでもいい。

/ ビュー遷移全体のルート要素に対する設定 /
::view-transition-group(root) {
/ アニメーションのデュレーションを一括管理。少しキビキビ動く0.3秒が実務では好まれる /
animation-duration: 0.3s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); / お馴染みのMaterial Design等で使われる滑らかなイージング /
}

/ 遷移前の状態(古い画面)の制御 /
::view-transition-old(root) {
/ デフォルトのクロスフェード(mix-blend-mode: normal)を維持しつつ、独自の動きをさせる /
animation-name: slide-to-left;
}

/ 遷移後の状態(新しい画面)の制御 /
::view-transition-new(root) {
animation-name: slide-from-right;
}

/ — ここから独自キーフレームアニメーションの定義 — /

@keyframes slide-to-left {
from {
transform: translateX(0);
opacity: 1;
}
to {
/ 古い画面を左側にスライドアウトさせながら消す /
transform: translateX(-30%);
opacity: 0;
}
}

@keyframes slide-from-right {
from {
/ 新しい画面を右側からスライドインさせる /
transform: translateX(30%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

このコードのポイントは、`::view-transition-old(root)` に対して明示的に `animation-name` を指定し、古い画面が「ただ消える」のではなく「左へ押し出されるように消える」演出を作っている点だ。これだけで、一気にSPAらしいリッチな遷移に生まれ変わる。

—

3. 応用編:特定要素(Shared Element Transition)の個別制御

全画面がグニャッと動くだけじゃ、ワンランク上のUIとは言えない。
例えば、プロダクト一覧ページから詳細ページへ移動するとき、「サムネイル画像が、そのまま詳細画面のメイン画像へスルスルと移動する」あのリッチな演出を実装したいよね?

ここで `view-transition-name` というCSSプロパティと、`::view-transition-old` の組み合わせが真価を発揮する。

実装サンプル:カードから詳細へのスムーズな画像遷移

HTML側(共通の固有ネームを付与するのがコツ)

商品

ここに詳細な説明が入ります……

CSS側(`::view-transition-old` を使った微調整)
実は、同じ `view-transition-name` を持つ要素同士は、ブラウザが自動的に古い位置から新しい位置への移動(Morphing)を計算してくれる。しかし、アニメーションの質感をさらにリッチにするために、`old` 側の挙動を次のようにハックすることがある。

/ 特定のIDを持つビュー遷移グループの古い状態を制御 /
::view-transition-old(product-image-1) {
/
古い画像が消えるときのトランスフォームやブレンドモードを調整。
例えば、移動中に少しだけスケールを縮小させたい場合など。
/
animation: 0.3s cubic-bezier(0.25, 1, 0.5, 1) both fade-and-shrink;
}

::view-transition-new(product-image-1) {
animation: 0.3s cubic-bezier(0.25, 1, 0.5, 1) both fade-and-grow;
}

@keyframes fade-and-shrink {
0% {
transform: scale(1);
opacity: 1;
}
100% {
transform: scale(0.95); / ほんのり縮みながら消える /
opacity: 0;
}
}

@keyframes fade-and-grow {
0% {
transform: scale(1.05);
opacity: 0;
}
100% {
transform: scale(1); / ほんのり大きめの状態からジャストサイズに収まる /
opacity: 1;
}
}

このアプローチを取ることで、「ただの画像切り替え」ではなく、ユーザーの視線を自然に誘導する、UX的に極めて優れた画面遷移が実現できるんだ。

—

4. 現場でハマりがちな「落とし穴」と、シニアの知見

さて、ここまで読んで「よし、明日から全部の要素にビュー遷移を仕掛けまくるぞ!」と思ったそこの君、ちょっと待ってくれ。
現場でこのAPIを触る際、誰もが一度はハマる「地雷原」がある。先輩からの愛の戒めとして、いくつか共有しておこう。

1. `view-transition-name` の重複は絶対にNG

CSSのIDセレクタと同じで、同一ドキュメント内に同じ `view-transition-name` が複数存在してはならない。ブラウザが「どれとどれを繋げばいいのか」パニックを起こし、遷移そのものがエラー(無視)になるか、予期せぬレイアウト崩壊を引き起こす。
リスト要素などに動的に付与する場合は、必ず一意のID(例: `item-${id}`)をJSやテンプレートエンジンで埋め込むこと。

2. パフォーマンスの呪縛(巨大な画像をキャプチャするコスト)

`::view-transition-old` は、あくまで「ブラウザが撮影した静止画(テクスチャ)」だ。
もし、ページ内にバカでかい高解像度の背景画像や、複雑なSVGグラフィックが大量にある状態でビュー遷移を発火させると、撮影(キャプチャ)の瞬間ジェイムズ・ウェッブ宇宙望遠鏡なみの負荷がメインスレッドにかかり、盛大にカクつく。
アニメーションさせる要素の範囲は必要最小限に絞り、不要な要素には `view-transition-name` を濫用しないこと。これ、鉄則ね。

3. prefers-reduced-motion への配慮を忘れるな

動きの激しい画面遷移は、前庭系疾患を持つユーザーや、激しいアニメーションを好まないユーザーにとって苦痛でしかない。Accessibility(アクセシビリティ)の観点からも、メディアクエリで必ず殺す(または控えめにする)配慮を忘れないように。

@media (prefers-reduced-motion: reduce) {
::view-transition-group(),
::view-transition-old(),
::view-transition-new() {
animation: none !important;
}
}

こういう細やかな気配りができるエンジニアを、チームは求めているんだ。

—

まとめ

`::view-transition-old` 擬似要素は、単なる「古い画面の残骸」ではない。
私たちがフロントエンドの表現力を限界突破させ、ユーザーに「おっ、このサイト、サクサク動いて気持ちいいな」と感じさせるための、強力無比な武器だ。

仕様はまだ進化の途中だが、モダンブラウザ(Chrome, Edge, Safariのキャッチアップなど)のサポートも着実に進んでいる。今、この技術をモノにしておけば、間違いなくチーム内で「おっ」と言わせるフロントエンドエンジニアになれるはずだ。

さて、理屈はここまでだ。お前のエディタを開いて、まずはローカルのプロジェクトでこのコードを試してみてくれ。うまくいったら、コーヒーでも飲みながらその滑らかな動きに酔いしれるといい。
それじゃあ、また次のコードレビューの現場で会おう!

コメント

タイトルとURLをコピーしました