【実務・中級編】 ::view-transition-group 擬似要素の階層構造 – CSS実践ガイド

おい、最近View Transitions API触ってるか?
「ページ遷移がパッと滑らかになるあれね、知ってる知ってる」なんて言って、お茶を濁してないだろうな。

モダンブラウザの進化はすさまじいが、こと「DOMが書き換わる瞬間のアニメーション」に関しては、かつては俺たちフロントエンド勢はJavaScriptでDOMを必死にキャプチャして、ゴリゴリと面倒な座標計算をするしかなかった。だが、時代は変わった。CSSだけで、しかもブラウザのネイティブエンジンに裏打ちされた滑らかな画面遷移が実現できるようになったんだ。

そのView Transitions APIの心臓部であり、アニメーションの舞台裏を完全に支配するのが、今日深掘りする `::view-transition-group` 擬似要素 だ。

公式ドキュメントや浅い解説記事だと、「画面遷移のグループを指定するセレクタです」くらいで片付けられがちだが、実務でこれを使いこなすためには、ブラウザが裏側で何をやっているか(DOMツリーの裏で生成されるシャドウDOM的な構造)を完全に理解している必要がある。

今日は、中級からもう一段階上のシニアへステップアップしたい君に向けて、この `::view-transition-group` の階層構造と、現場で使える実践的な知見を叩き込んでやろう。

—

1. ブラウザの裏側で何が起きているのか?仕様の解剖

まず、View Transitions APIがトリガーされたとき、ブラウザの内部で何が起こるか知ってるか?
`document.startViewTransition()` が実行された瞬間、ブラウザは現在の画面のスナップショットを撮り、DOMを更新したあと、新しい画面のスナップショットも撮る。

そして、その2つのスナップショットをアニメーションさせるために、文書のルート(``要素)の直下に、一時的に専用の pseudo-DOM(疑似的なDOMツリー)を構築するんだ。

その階層構造のツリーがこれだ。

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

この構造、めちゃくちゃ重要だから脳みそに焼き付けておいてくれ。それぞれの役割を上から順に紐解いていくぞ。

  • `::view-transition`

画面全体を覆うルートのコンテナ。いわば舞台の全体像だ。

  • `::view-transition-group()`

ここが今日の主役。遷移する要素の「空間(ボックス)」そのものを定義する。位置とサイズの変化(FLIPアニメーションの原理)を担うのはこいつだ。

  • `::view-transition-image-pair()`

古い画面(old)と新しい画面(new)を包み込むコンテナ。クロスフェードなどのブレンドを管理する。

  • `::view-transition-old()`

遷移「前」の画面のスクリーンショット画像。だんだん消えていく(opacity: 1 -> 0)。

  • `::view-transition-new()`

遷移「後」の画面のスクリーンショット画像。だんだん現れてくる(opacity: 0 -> 1)。

この階層構造を理解していないと、「なぜか要素が意図しないサイズに引き伸ばされる」「アニメーションの途中でカクつく」といった現場特有のバグに直面したとき、どこをCSSで叩けばいいか分からなくなるんだ。

—

2. 実務で直面する課題:「位置とサイズの変化」をグループで制御する

よくあるユースケースとして、「一覧ページのサムネイル画像」から「詳細ページのメイン画像」へ滑らかに変形して遷移する演出(いわゆるShared Element Transition)を考えてみよう。

ここで `::view-transition-group` がどう動くか。
ブラウザは、遷移前の要素の位置・サイズと、遷移後の要素の位置・サイズを自動的に検出し、`::view-transition-group(my-image)` に対して `transform` と `width`/`height` のトランジションを自動的に適用してくれる。

だが、ここでシニアとして知っておくべき極意がある。
「デフォルトのままだと、ブラウザは古い画像から新しい画像へ、単にクロスフェード(重ね合わせ)しながらサイズを変えるだけ」なんだ。これだと、画像アスペクト比が違う場合にビュッと歪んだりして、非常にダサい。

そこで、`::view-transition-group` に対して適切なCSSプロパティを指定し、アニメーションの挙動を調律してやる必要がある。

—

3. コピペで使える!実践的プロダクションコード

百聞は一見に如かずだ。実際のプロジェクトでそのまま使える、洗練されたCSSのサンプルコードを見せよう。

HTML側で、遷移させたい要素に固有の `view-transition-name` を付与しておく。


サムネイル


メイン画像

そして、CSS(あるいはSCSS)でこのグループを徹底的にコントロールする。

/ ————————————————————————–

  • View Transitions API: グループ階層のチューニング
  • ————————————————————————– /

エネルギーに満ちた滑らかな変形を実現するためのスタイル定義

/ 1. アニメーション全体のイージングをカスタムプロパティで一元管理する /
:root {
–transition-easing: cubic-bezier(0.25, 1, 0.5, 1);
–transition-duration: 0.5s;
}

/ 2. グループ(空間と位置の移動)の制御 /
::view-transition-group(product-visual) {
/ 位置とサイズの移行にかかる時間とイージングを指定 /
animation-duration: var(–transition-duration);
animation-timing-function: var(–transition-easing);
}

/ 3. 新旧画像のクロスフェードの挙動を上書きする
デフォルトのクロスフェードだと画像が透けてチカチカするので、
スライドやスケール感を調整してリッチな質感にする /
::view-transition-old(product-visual),
::view-transition-new(product-visual) {
animation-duration: var(–transition-duration);
animation-timing-function: var(–transition-easing);
/ 親要素からはみ出た部分をどう扱うか /
overflow: hidden;
}

/ 4. 遷移前の画像(old)のカスタムアニメーション /
::view-transition-old(product-visual) {
/ デフォルトのフェードアウトに加え、少しだけ縮小させて奥行き感を出す /
animation-name: fade-out-scale, 300ms ease-out;
}

/ 5. 遷移後の画像(new)のカスタムアニメーション /
::view-transition-new(product-visual) {
/ 背後からフワッとスライドインさせるような演出 /
animation-name: fade-in-slide;
}

/ キーフレーム定義の例 /
@keyframes fade-out-scale {
from {
opacity: 1;
transform: scale(1);
}
to {
opacity: 0;
transform: scale(0.95);
}
}

@keyframes fade-in-slide {
from {
opacity: 0;
transform: translateY(20px) scale(1.05);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}

このコードのミソは、`::view-transition-group` で全体の骨格(位置・サイズ移動)のタイミングを制御しつつ、中の `old` と `new` に対して個別のキーフレームアニメーションをあてがっている点だ。
この分離ができるようになると、アニメーションの品質がプロのそれへと一気に引き上がる。

—

4. 現場でハマりがちな「沼」と回避策

最後に、実務でこの `::view-transition-group` を触ったとき、9割のエンジニアがハマる「罠」をシェアしておこう。

罠1: `view-transition-name` の重複

これ、絶対にやるな。DOMツリー内で `view-transition-name` の値はユニーク(一意)である必要がある。同じページ内に同じ名前のグループが複数存在すると、ブラウザがどの要素とどの要素を紐づければいいかパニックを起こし、View Transition自体が安全のためにサイレント無効化される。
動的にリストアイテムを生成する場合などは、JavaScript側でインデックスや一意のIDを組み込んで名前を動的に付与してやる配慮が必要だ。

罠2: パフォーマンスの低下(レイヤー爆発)

`::view-transition-group` は、ブラウザにGPUレイヤー(合成レイヤー)を強制的に生成させる。安易にページ内のあらゆる要素(例えばリストの全アイテムなど)にこの名前を付与しまくると、メモリを食いつぶし、逆にカクつく原因になる。
ユーザーの視線が集まる「ヒーロー要素」や「ナビゲーション」など、演出として効果的な箇所に絞って適用するのが、シニアとしての正しい判断だ。

—

まとめ

`::view-transition-group` 擬似要素の階層構造、腑に落ちただろうか?

ただのセレクタとして見るのではなく、「ブラウザが一時的に生成するアニメーション専用のステージ構造」として捉えることで、CSSの効かせ方がガラリと変わったはずだ。

フロントエンドのトレンドは早い。だが、こうしたブラウザの描画の仕組み(レンダリングエンジンレベルの挙動)に裏打ちされた知識は、フレームワークがどれだけ変わろうとも決して色褪せない、君の強力な武器になる。

さあ、エディタを開いて、単調なページ遷移をリッチな体験へと生まれ変わらせてこい!

コメント

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