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

公開日: 2025 年 9 月 22 日

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

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

  • クリッピング(overflowclip-pathborder-radius): クリッピングは要素の子に影響します。つまり、ビュー トランジション グループの兄弟は互いにクリップできません。
  • opacitymask-imagefilter: 同様に、これらの効果はツリーの完全にラスタライズされた画像で動作するように設計されており、各アイテムに個別に影響を与えるのではなく、子に影響を与えます。
  • 3D 変換(transform-styletransformperspective): 3D 変換アニメーションの全範囲を表示するには、階層を維持する必要があります。

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

ビューの切り替え中にクリッピング効果が壊れたことを記録します。
テキストはダイアログでクリップされるはずですが、クリップされていません。アニメーションの速度を遅くして効果を強調しています。

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

Browser Support

  • Chrome: 140.
  • Edge: not supported.
  • 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-groupcontain に設定して、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-image-pair(card)
  │  │  │  ├─ ::view-transition-old(card)
  │  │  │  └─ ::view-transition-new(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-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)と同じ形状とボーダーの太さの透明なボーダーが与えられます。

クリッピングなど

ネストされたビュー遷移グループは、クリッピング効果以外の場所でも使用されます。別の例として、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 ツリーのトポロジの一部を保持できます。これにより、ビューの切り替えではこれまで実現できなかったさまざまな効果が実現可能になります。その一部については、こちらで説明しています。

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