【実務・中級編】 ::view-transition 擬似要素による画面遷移アニメーション – CSS実践ガイド

やあ、お疲れ。プロジェクトの進捗はどうだい?
そろそろSPAのようなシームレスな画面遷移を、面倒なJavaScriptのルーティングライブラリや複雑なアニメーション制御なしで実現したい、なんて考えてる頃なんじゃないかと思ってね。

今日は、現在のモダンブラウザにおけるフロントエンドのパラダイムシフトと言っても過言ではない「View Transitions API」、そしてその肝となる`::view-transition` 擬似要素について、現場のリアルな実装知見を交えながら徹底的に解説しようと思う。

ぶっちゃけ、これまでのCSSアニメーションとJavaScriptの組み合わせによる画面遷移の制御は、DOMの破棄タイミングや非同期処理のタイミング合わせで地獄を見ることも多かったはずだ。だが、このAPIとCSSの組み合わせを知れば、君のワークフローは劇的に変わる。心して聞いてくれ。

—

1. View Transitions API とは何か?(ブラウザの裏側の世界)

まず、仕様の表面的な使い方を覚える前に、ブラウザが裏側で何をやっているのかを理解しよう。ここをサボると、レイアウト崩壊や意図しないアニメーションのバグを踏んだときに一切デバッグができなくなる。

APIの `document.startViewTransition()` が呼ばれた瞬間、ブラウザは裏側で以下の恐ろしいほど緻密な処理をミリ秒単位の世界で実行している。

1. 現在の状態のスナップショット撮影:
ブラウザは、DOMが書き換わる直前の画面全体(あるいは指定要素)の「スクリーンショット(正確にはピクセルデータ)」を静的にキャプチャする。
2. DOMの更新:
コールバック関数が実行され、DOMツリーが新しい状態に書き換わる。
3. 新しい状態のスナップショット撮影:
新しいDOMが描画された直後の状態を、もう一度キャプチャする。
4. 擬似要素ツリーの構築:
ブラウザはドキュメントのルート(``)の直下に、アニメーション専用の「疑似要素ツリー」を一時的に生成する。これが、今回主役となる `::view-transition` の世界だ。
5. アニメーションの実行:
古い画面のキャプチャから新しい画面のキャプチャへ、CSS Animationを使ってクロスフェード(またはカスタムアニメーション)を適用する。
6. お片付け:
アニメーションが終わると、疑似要素ツリーはキレイに消え去り、通常のDOM描画に戻る。

つまり、開発者が書くCSSは、「一時的に生成されるブラウザ特製のスクリーンショット群をどう料理するか」の指示書に他ならないんだ。

—

2. 擬似要素の構造を丸裸にする

では、ブラウザが生成する疑似要素の階層構造を覗いてみよう。CSSセレクタを書く上で、このDOM(いや、シャドウDOM的なツリー構造)の階層を理解していないと全くスタイリングが効かない。

ブラウザの内部では、以下のような構造のツリーが動的に組み立てられている。

::view-transition
├── ::view-transition-group(root) <-- アニメーションの「箱(位置・サイズ)」 │ ├── ::view-transition-image-pair(root) <-- 新旧画像のペアレントコンテナ │ │ ├── ::view-transition-old(root) <-- 古い画面のスクリーンショット │ │ └── ::view-transition-new(root) <-- 新しい画面のスクリーンショット それぞれの役割を現場の感覚でざっくり解説しておこう。

  • `::view-transition`: 全体を包むルートコンテナ。半透明にしたり、背景色を変えたりできるが、基本は触らないことが多い。
  • `::view-transition-group()`: アニメーションの対象となる「グループ」。ここに対して `transform` や `width`, `height` が自動で計算され、滑らかな位置・サイズのモーフィング(変形)が行われる。一番マジックを感じる部分だ。
  • `::view-transition-image-pair()`: 古い画像と新しい画像を重ね合わせるコンテナ。
  • `::view-transition-old()`: 遷移前の画面。デフォルトでは `opacity: 1 -> 0` のフェードアウトがかかる。
  • `::view-transition-new()`: 遷移後の画面。デフォルトでは `opacity: 0 -> 1` のフェードインがかかる。

—

3. 実務で即コピペできる!実践的コードとレシピ

理論はこれくらいにして、実際に手を動かしてみよう。
今回は、中級者なら誰もが実装したくなる「要素の滑らかな位置移動と、全体のクロスフェードを組み合わせたリッチな画面遷移」のサンプルコードだ。

HTML / JavaScript の準備

まずはトリガーとなるJavaScriptとHTML。SPAのページ切り替え、あるいは同一ページ内の状態変化を想定してほしい。

CSSの極意:デフォルトの挙動をハックする

ここからが本番だ。ブラウザのデフォルトのクロスフェードは、実はちょっと「ボヤァ」としていてダサい。これを今風のキレのあるアニメーションに仕立て上げる。

/ 1. デフォルトのクロスフェード時間をカスタムする /
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.4s;
animation-timing-function: cubic-bezier(0.25, 1, 0.5, 1); / エラスティック感のあるイージング /
}

/ 2. 特定の要素(カード)に固有の「名前」を付与してリンクさせる /
.card {
/ これが魔法のプロパティ。DOMが変っても、この名前が同じならブラウザが同一要素とみなして追跡する /
view-transition-name: hero-card;
}

/ 3. 個別要素(hero-card)のトランジション挙動を完全にコントロールする /
::view-transition-group(hero-card) {
/ 位置とサイズの移動アニメーションをリッチにする /
animation-duration: 0.5s;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}

/ 古いカードが消えるときのアニメーションをカスタム /
::view-transition-old(hero-card) {
animation: 0.3s ease-in both fade-out, 0.5s cubic-bezier(0.16, 1, 0.3, 1) both slide-out;
}

/ 新しいカードが現れるときのアニメーションをカスタム /
::view-transition-new(hero-card) {
animation: 0.3s ease-out 0.1s both fade-in;
}

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

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

@keyframes slide-out {
from { transform: translateY(0); }
to { transform: translateY(-20px); }
}

どうだい? この `view-transition-name` さえ指定してしまえば、DOMツリー上の位置が離れていようが、構造がガラリと変わろうが、ブラウザが勝手に「元はこの要素だったんだな」と判断してモーフィングアニメーションを計算してくれる。これがこのAPIの真骨頂だ。

—

4. 現場でハマりがちな「アンチパターン」と注意点

最後に、シニアとして君にどうしても伝えておきたい「現場の地雷」をいくつかシェアしておく。これを踏まないと、リリース直後にQAからバグチケットの嵐を食らうことになるから注意してくれ。

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

CSSのセレクタやIDと同じ感覚で、同一ページ内に同じ `view-transition-name` を複数存在させてはならない。ブラウザが「どことどこを紐づければいいんだよ!」とパニックを起こし、トランジションが正しく発火しなくなるか、予期せぬ描画バグを引き起こす。
動的にリストアイテムを生成する場合などは、JS側で一意な名前(例: `item-1`, `item-2` など)を動的に付与する設計にしよう。

② 重すぎる要素を対象にしない

スナップショットを「ピクセルデータ」としてキャプチャしてアニメーションさせるということは、対象要素が巨大であったり、重いCSSフィルター(`backdrop-filter` や複雑なシャドウの多用)がかかっていると、アニメーション中に盛大にフレームレートが落ちる(カクつく)。
リッチに見せたいからといって画面全体を無闇にアニメーションさせるのは避け、動かしたいキーパーツ(カード、ヘッダー、サムネイルなど)に絞って適用するのが、プロのパフォーマンスチューニングだ。

③ prefers-reduced-motion への配慮を忘れない

アクセシビリティの観点を忘れるな。前庭覚(平衡感覚)に敏感なユーザーにとって、意図しない画面全体のダイナミックな移動やフェードは、軽いめまい(V系サイトのあれだ)を引き起こす原因になる。
実務では必ず以下のようなメディアクエリでアニメーションを無効化する配慮を挟むこと。

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

—

おわりに

View Transitions APIと `::view-transition` 擬似要素は、これまでの「JavaScriptで無理やりDOMアニメーションを同期させる」というフロントエンドの泥臭い歴史を塗り替える、非常にエレガントなソリューションだ。

仕様のキャッチアップは最初こそ取っつきにくく感じるかもしれないが、ブラウザの裏側の挙動(スナップショットと疑似要素ツリー)さえ頭に入っていれば、どんな複雑なレイアウト遷移も怖くない。

さあ、今日の業務のコードに、少しだけこの滑らかな魔法を取り入れてみないか?
君のプロダクトが、一段と洗練された手触りの良いアプリケーションに生まれ変わるはずだ。質問があったらいつでも声をかけてくれ。それじゃあ、実装に戻るとしようか。

コメント

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