ネストされたビュー遷移グループを使用して、ビュー遷移でのクリッピングの問題などを回避する

公開日: 2025 年 9 月 22 日

ビュー遷移を開始すると、ブラウザは view-transition-name でタグ付けされた要素の遷移前と遷移後のスナップショットを自動的に取得します。これらのスナップショットは、疑似要素のツリーでレンダリングされます。デフォルトでは、生成されるツリーは「フラット」です。つまり、DOM の元の階層は失われ、キャプチャされたすべてのビュー遷移グループは、1 つの ::view-transition 疑似要素の下の兄弟になります。

このフラットツリー アプローチは多くのユースケースで十分ですが、このアプローチでは実現できないスタイル設定のユースケースもあります。フラットツリーで予期しない視覚効果をもたらす可能性のある効果の例を次に示します。

  • クリッピング(overflow、clip-path、border-radius): クリッピングは要素の子に影響します。つまり、ビュー遷移グループの兄弟は互いにクリップできません。
  • opacity、mask-image、filter: 同様に、これらのエフェクトは、各アイテムに個別に影響するのではなく、ツリーの完全にラスタライズされた画像に作用して、子に影響するように設計されています。
  • 3D 変換(transform-style、transform、perspective): 3D 変換アニメーションをすべて表示するには、一部の階層を維持する必要があります。

次の例は、DOM ツリー内の祖先によってクリップされた要素を含む、フラットな疑似ツリーを示しています。これらの要素は、ビュー遷移中にクリッピングが失われ、視覚効果が損なわれます。

ビュー遷移の実行中にクリッピング効果が壊れている録画。テキストはダイアログによってクリップされるはずですが、クリップされていません。アニメーションの速度を遅くして、効果を強調しています。

ネストされたビュー遷移グループは、ビュー遷移の拡張機能であり、::view-transition-group 疑似要素を相互にネストできます。ビュー遷移グループがネストされている場合、遷移中にクリッピングなどの効果を復元できます。

Browser Support

  • Chrome: 140.
  • Edge: 140.
  • Firefox: not supported.
  • Safari: not supported.

フラットな疑似ツリーからネストされた疑似ツリーへ

次のデモでは、ユーザーのアバターをクリックすると、そのユーザーの詳細情報が表示されます。アニメーションは、クリックされたボタンをダイアログにモーフィングし、アバターと名前を画面上で移動し、ダイアログの段落を上下にスライドする同一ドキュメント ビュー遷移によって処理されます。

ライブデモ

デモの記録

デモの録画(スロー再生)

デモをよく見ると、遷移に問題があることがわかります。説明を含む段落は DOM の <dialog> 要素の子ですが、遷移中にテキストが <dialog> のボックスでクリップされません。

<dialog id="info_bramus" closedby="any">
  <h2><img alt="…" class="avatar" height="96" width="96" src="avatar_bramus.jpg"> <span>Bramus</span></h2>
  <p>Bramus is …</p>
  <p>…</p>
</dialog>

<dialog> に overflow: clip を適用しても何も起こりません。

問題は、ビュー遷移が疑似ツリーを構築してレンダリングする方法にあります。

  • 疑似ツリーでは、デフォルトですべてのスナップショットが互いに兄弟関係になります。
  • 疑似ツリーは、ドキュメント全体の上にレンダリングされる ::view-transition 疑似要素にレンダリングされます。

このデモでは、DOM ツリーは次のようになります。

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(card)
  │  │  └─ ::view-transition-image-pair(card)
  │  │     ├─ ::view-transition-old(card)
  │  │     └─ ::view-transition-new(card)
  │  ├─ ::view-transition-group(name)
  │  │  └─ ::view-transition-image-pair(name)
  │  │     ├─ ::view-transition-old(name)
  │  │     └─ ::view-transition-new(name)
  │  ├─ ::view-transition-group(avatar)
  │  │  └─ ::view-transition-image-pair(avatar)
  │  │     ├─ ::view-transition-old(avatar)
  │  │     └─ ::view-transition-new(avatar)
  │  ├─ ::view-transition-group(paragraph1.text)
  │  │  └─ ::view-transition-image-pair(paragraph1.text)
  │  │     └─ ::view-transition-new(paragraph1.text)
  │  └─ ::view-transition-group(paragraph2.text)
  │     └─ ::view-transition-image-pair(paragraph2.text)
  │        └─ ::view-transition-new(paragraph2.text)
  ├─ head
  └─ body
        └─ …

::view-transition-group(.text) 疑似要素は ::view-transition-group(card) 疑似要素の後続の兄弟であるため、カードの上に描画されます。

::view-transition-group(card) が ::view-transition-group(.text) をクリップするには、::view-transition-group(.text) 疑似要素が ::view-transition-group(card) の子である必要があります。これには、生成された ::view-transition-group() 疑似要素に「親グループ」を割り当てることができる view-transition-group を使用します。

親グループを変更するには、次の 2 つの方法があります。

  • 親で view-transition-group を contain に設定して、view-transition-name を持つすべての子が含まれるようにします。
  • すべての子で、view-transition-group を親の view-transition-name に設定します。nearest を使用して、最も近い祖先グループをターゲットにすることもできます。

このデモでネストされたビュー遷移グループを使用するには、コードは次のようになります。

button.clicked,
dialog {
  view-transition-group: contain;
}

または

button.clicked,
dialog *,
  view-transition-group: nearest;
}

このコードを配置すると、::view-transition-group(.text) 疑似が ::view-transition-group(card) 疑似内にネストされます。これは、すべてのネストされた疑似をまとめる追加の ::view-transition-group-children(…) 疑似で行われます。

html
  ├─ ::view-transition
  │  ├─ ::view-transition-group(card)
  │  │  ├─::view-transition-group-children(card)
  │  │  │ ├─ ::view-transition-group(paragraph1.text)
  │  │  │ │  └─ ::view-transition-image-pair(paragraph1.text)
  │  │  │ │     └─ ::view-transition-new(paragraph1.text)
  │  │  │ └─ ::view-transition-group(paragraph2.text)
  │  │  │    └─ ::view-transition-image-pair(paragraph2.text)
  │  │  │       └─ ::view-transition-new(paragraph2.text)
  │  │  └─ ::view-transition-image-pair(card)
  │  │     ├─ ::view-transition-old(card)
  │  │     └─ ::view-transition-new(card)
  │  ├─ ::view-transition-group(name)
  │  │  └─ ::view-transition-image-pair(name)
  │  │     ├─ ::view-transition-old(name)
  │  │     └─ ::view-transition-new(name)
  │  └─ ::view-transition-group(avatar)
  │     └─ ::view-transition-image-pair(avatar)
  │        ├─ ::view-transition-old(avatar)
  │        └─ ::view-transition-new(avatar)
  ├─ head
  └─ body
        └─ …

最後に、::view-transition-group(card) 疑似要素で段落をクリップするには、::view-transition-group-children(card) 疑似要素に overflow: clip を適用します。

::view-transition-group-children(card) {
  overflow: clip;
}

結果は次のようになります。

ライブデモ

デモの記録

デモの録画(スロー再生)

::view-transition-group-children 疑似は、ネストされたグループが使用されている場合にのみ存在します。元の要素の border-box に合わせてサイズが調整され、疑似要素を生成した要素(前の例では card)と同じ形状と枠線の太さを持つ透明な枠線が設定されます。

クリッピングなど

ネストされたビュー遷移グループは、クリッピング効果以外の場所で使用されます。もう 1 つの例は 3D 効果です。次のデモでは、遷移中にカードを 3D で回転させるオプションがあります。

html:active-view-transition-type(open) {
    &::view-transition-old(card) {
        animation-name: rotate-out;
    }
    &::view-transition-new(card) {
        animation-name: rotate-in;
    }
}
html:active-view-transition-type(close) {
    &::view-transition-old(card) {
        animation-name: rotate-in;
    }
    &::view-transition-new(card) {
        animation-name: rotate-out;
    }
}

ネストされたビュー遷移グループがない場合、アバターと名前はカードと一緒に回転しません。

ライブデモ

デモの記録

デモの録画(スロー再生)

アバターと名前の疑似要素をカード内にネストすることで、3D 効果を復元できます。ただし、必要な作業はこれだけではありません。::view-transition-old(card) と ::view-transition-new(card) の疑似要素を回転させるだけでなく、::view-transition-group-children(card) の疑似要素も回転させる必要があります。

html:active-view-transition-type(open) {
    &::view-transition-group-children(card) {
        animation: rotate-in var(--duration) ease;
        backface-visibility: hidden;
    }
}
html:active-view-transition-type(close) {
    &::view-transition-group-children(card) {
        animation: rotate-out var(--duration) ease;
        backface-visibility: hidden;
    }
}

ライブデモ

デモの記録

デモの録画(スロー再生)

その他のデモ

次の例では、ネストされたビュー遷移グループを使用して、カードが祖先スクローラーによってクリップされるようにしています。付属のコントロールを使用して、ネストされたビュー遷移グループの使用をオンまたはオフに切り替えることができます。

ライブデモ

デモの記録

このデモの興味深い点は、すべての ::view-transition-group(.card) 疑似要素が、祖先の ::view-transition-group(cards) 疑似要素内にネストされ、クリップされることです。#targeted-card は、その開始 / 終了アニメーションが ::view-transition-group(cards) によってクリップされないようにするため、除外されています。

/* The .cards wrapper contains all children */
.cards {
  view-transition-name: cards;
  view-transition-group: contain;
}

/* Contents that bleed out get clipped */
&::view-transition-group-children(cards) {
  overflow: clip;
}

/* Each card is given a v-t-name and v-t-class */
.card {
  view-transition-name: match-element;
  view-transition-class: card;
}

/* The targeted card is given a unique name (to style the pseudo differently)
   and shouldn't be contained by the ::view-transition-group-children(cards) pseudo */
#targeted-card {
  view-transition-name: targeted-card;
  view-transition-group: none;
}

内容のまとめ

ネストされたビュー遷移を使用すると、疑似要素を構築するときに DOM ツリーのトポロジの一部を保持できます。これにより、以前はビュー遷移では不可能だったさまざまな効果が実現します。その一部については、ここで説明しました。

ネストすると、ビュー遷移の構築モデルが変更されます。これは、高度な効果を作成するために使用されます。前述のように、要素スコープのビュー遷移でも、よりシンプルなモデルで効果の一部を実現できます。両方の機能を試して、ニーズに最適な機能を選択することをおすすめします。