要素スコープのビュー遷移を使用して、同時実行のネストされたビュー遷移を実行する

公開日: 2026 年 3 月 27 日

要素スコープのビュー遷移を使用すると、複数のビュー遷移を同時に実行したり、進行中のビュー遷移を別のビュー遷移内にネストしたり、ドキュメント スコープのビュー遷移で発生する可能性のある z-index の問題を解決したりできます。これらすべてを、ページの残りの部分のインタラクティブ性を維持しながら行うことができます。使用方法については、こちらのガイドをご覧ください。

プロモーション動画: スコープ付きビュー トランジションでウェブを再構築する。ライブデモを試す(Chrome 147 以降)

より狭い範囲のスコープビュー遷移の必要性

document.startViewTransition()(またはそのドキュメント間の対応物)で同じドキュメントのビュー遷移を開始すると、ブラウザは結果のビュー遷移をドキュメントにスコープします。

更新コールバックが実行され、ブラウザが必要な要素をすべてスナップショットすると、結果として得られた ::view-transition オーバーレイとその疑似要素のツリーが、次の例の :root 要素 html にアタッチされます。

html
  ├─ ::view-transition
  │  └─ ::view-transition-group(root)
  │     └─ ::view-transition-image-pair(root)
  │        ├─ ::view-transition-old(root)
  │        └─ ::view-transition-new(root)
  ├─ head
  └─ body
     └─ …

::view-transition レイヤは遷移ルートの上にレンダリングされるため、予期しない状況が発生する可能性があります。たとえば、ビューの切り替えに参加している要素が、突然他の参加していない要素と重なったり、ビューの切り替え中に要素が祖先ラッパーによってクリップされなくなったりする可能性があります。

ライブデモ

デモの記録

::view-transitionpointer-events を再度有効にするか、ネストされたビュー遷移グループを使用することで、ドキュメント スコープのビュー遷移によって発生する副作用の一部を解決できます。ただし、これらの方法ではすべての問題を解決できるわけではありません。

たとえば、position: fixed やポップオーバーを含む要素は、遷移がアクティブな間、ドキュメント スコープのビュー遷移によって隠されたままになります。これは z-index の問題とも呼ばれます。

次のデモでポップオーバーを切り替え、[シャッフル] ボタンを選択して、ドキュメント スコープのビュー遷移を開始します。ネストされたビュー トランジション グループはクリッピングの問題を解決しますが、レイヤリングの問題は残ります。

ライブデモ

デモの記録

回避策の 1 つは、view-transition-name を指定して、ビューの遷移の一部として popover をキャプチャすることです。この方法は単一インスタンスでは機能する可能性がありますが、メンテナンスが煩雑になり、スナップショット プロセスに不必要な負荷がかかります。

要素スコープのビュー遷移

要素スコープのビュー遷移を使用すると、DOM のサブツリーでビュー遷移を開始できます。document.startViewTransition() を呼び出す代わりに、任意の要素で element.startViewTransition() を呼び出します。これにより、ビューの切り替えがその要素にスコープされます。

次のスニペットでは、ブラウザが <ul> 要素で要素スコープのビュー遷移を開始します。

document.querySelector('ul').startViewTransition({
  callback: () => {
    // … code that manipulates the contents of <ul>
  },
})

element.startViewTransition() を呼び出す要素(<ul> など)は、トランジション ルートまたはスコープと呼ばれます。

ブラウザがビュー遷移を要素にスコープすると、その要素は DOM の他の部分から分離されます。

  • ブラウザは、スナップショットの要素をスコープのサブツリー内でのみ検索します。
  • スナップショット作成プロセス中(update コールバックの実行中)は、スコープのレンダリングのみが停止します。
  • 結果の ::view-transition 疑似ツリーは、遷移ルートに挿入されます。

たとえば、<ul> を使用すると、ビューの切り替えがアクティブな間、DOM ツリーは次のようになります。

html
  ├─ head
  └─ body
     ├─ ul
     │  ├─ ::view-transition
     │  │  └─ ::view-transition-group(root)
     │  │     ├─ ::view-transition-group-children(root)
     │  │     │  └─ …
     │  │     └─ ::view-transition-image-pair(root)
     │  │        ├─ ::view-transition-old(root)
     │  │        └─ ::view-transition-new(root)
     │  ├─ li
     │  ├─ li
     │  └─ li
     ├─ button#showpopover
     ├─ button#reorder
     └─ div#popover
        └─ p

::view-transition 疑似要素は、トランジション ルートと同じサイズと形状を持ち、トランジション ルートの上にのみレンダリングされます。そのため、トランジション ルート外の要素のレイヤリング順序が尊重されます。

たとえば、<ul> 要素の上に表示されているポップオーバーがあり、<ul> 要素で要素スコープのビュー遷移を開始すると、ポップオーバーはビュー遷移の疑似ツリーによって隠されません。

次のデモで試してみてください。ボタンは 2 つあります。1 つ目のボタンはポップオーバーを切り替え、2 つ目のボタンは要素スコープのビュー遷移を使用してリストアイテムの順序を変更します。

ライブデモ

デモの記録

要素スコープのビュー遷移が使用されているため、遷移がアクティブな間、ポップオーバーは <ul> 要素の上に表示されたままになります。

また、<ul> 要素の外にある要素(ボタンなど)は、スコープの一部ではないため、操作可能なままになります。

自己参加スコープとネストされたビュー遷移グループ

オーバーフローをクリップする要素(つまり、overflowhiddenscroll、または clip に設定されている要素)で要素スコープのビュー遷移を開始すると、ビュー遷移のコンテンツが視覚的にクリップされたままになります。

これは、要素スコープのビュー遷移が次の処理を自動的に行うためです。

  • スコープには view-transition-name: root が自動的に適用されるため、自己参加型になります。
  • スコープには view-transition-group: contain が自動的に適用され、ネストされたビューのトランジション グループが有効になります。
  • 結果として得られる ::view-transition-group-children(root) 疑似要素は、スコープ ルートがオーバーフローをクリップする場合、overflow: clip を使用してコンテンツを自動的にクリップします。これにより、疑似要素がトランジション ルートから視覚的に漏れ出すのを防ぎます。

これにより、要素スコープのビュー遷移で使用する CSS を、キャプチャする要素のみに絞ることができます。たとえば、リストのデモでは、CSS はリストアイテムに名前を追加するだけです。

ul li {
  view-transition-name: match-element;
  view-transition-class: album;
}

次のデモで試してみてください。これにより、自己参加をオーバーライドできます。スコープが自己参加型(デフォルトの動作)の場合、すべてが想定どおりに機能します。スコープが自己参加型でない場合、境界線がすぐに変更され、移行中にコンテンツがラッパーから漏れ出します。

ライブデモ

デモの記録

参考までに、このデモの自己参加の疑似ツリーは次のようになります。

html
  ├─ head
  └─ body
     ├─ ul
     │  ├─ ::view-transition
     │  │  └─ ::view-transition-group(root)
     │  │     ├─ ::view-transition-group-children(root)
     │  │     │  ├─ ::view-transition-group(item1)
     │  │     │  │  └─ ::view-transition-image-pair(item1)
     │  │     │  │     ├─ ::view-transition-old(item1)
     │  │     │  │     └─ ::view-transition-new(item1)
     │  │     │  ├─ ::view-transition-group(item2)
     │  │     │  │  └─ …
     │  │     │  …
     │  │     └─ ::view-transition-image-pair(root)
     │  │        ├─ ::view-transition-old(root)
     │  │        └─ ::view-transition-new(root)
     │  ├─ li
     │  ├─ li
     │  └─ li
     └─ button#reorder

遷移ルートである <ul> 要素はコンテンツを垂直方向にクリップするため、::view-transition-group-children(root) も自動的にクリップを適用します。

要素スコープのビュー遷移の同時実行

要素スコープのビュー遷移は分離して実行されるため、スコープが異なる場合は複数の要素スコープのビュー遷移を同時に実行できます。

次のデモには、リストごとに 1 つずつ、2 つの並べ替えボタンがあります。各ボタンは、それぞれのリストでのみ要素スコープのビュー遷移を開始します。両方のリストの DOM ツリーは重複していないため、2 つの要素スコープのビュー遷移を同時に分離して実行できます。

ライブデモ

デモの記録

この分離された性質により、異なるスコープ間で view-transition-name 値を再利用することもできます。名前がスコープ内で一意である限り、競合は発生しません。

ネストされた要素スコープのビュー遷移と view-transition-name の包含

複数の要素スコープのビュー遷移の DOM ツリーが重複している場合、view-transition-name 値の衝突が発生する可能性があります。このため、ブラウザはアクティブな要素スコープのビュー遷移に view-transition-scope: all を自動的に割り当て、このリスクを軽減します。

anchor-scopeanchor-name 値をスコープするのと同様に、view-transition-scope プロパティは view-transition-name 値が要素のサブツリーにスコープされるようにします。このプロパティは、スコープ設定する名前のリストである none、またはすべての値をスコープ設定する all を受け入れます。

view-transition-scope は、名前が漏洩するのを防ぐだけでなく、要素とそのコンテンツが外部の同時ビュー遷移によってキャプチャされるのを防ぎます。スナップショット プロセスがサブツリーをトラバースしてスナップショットを作成する要素を見つけるとき、view-transition-scope: all が適用されている要素(とそのサブツリー全体)は無視されます。これは、これらの要素がすでに別の要素スコープのビュー遷移に参加していることを前提としています。

次のデモは、前のデモのバリエーションです。リストの内容をシャッフルする 2 つのボタンに加えて、リストを入れ替える [入れ替え] ボタンもあります。#lists-wrapper.reversed クラスを切り替えると、スワップが処理されます。

ライブデモ

デモの記録

view-transition-scope: all はシャッフル トランジション中に自動的に適用されるため、シャッフル トランジションが進行中でも、同時実行の外側スワップ トランジションを開始できます。

view-transition-scope: all は、外側のトランジションで要素がスナップショットされるのを防ぐため、このデモでは <ul> 要素をラップする要素に view-transition-name 値も追加しています。

#list1-wrapper, #list2-wrapper {
  view-transition-name: attr(id type(<custom-ident>));
}

このデモの疑似ツリーは、2 つ目のリストのシャッフルを開始してから両方のリストを入れ替えると、次のようになります。

html
  ├─ head
  └─ body
     └─ #lists-wrapper.reversed (SCOPE)
        ├─ ::view-transition
        │  └─ ::view-transition-group(lists-wrapper)
        │     ├─ ::view-transition-group-children(lists-wrapper)
        │     │  ├─ ::view-transition-group(list1-wrapper)
        │     │  │  └─ ::view-transition-image-pair(list1-wrapper)
        │     │  │     ├─ ::view-transition-old(list1-wrapper)
        │     │  │     └─ ::view-transition-new(list1-wrapper)
        │     │  └─ ::view-transition-group(list2-wrapper)
        │     │     └─ ::view-transition-image-pair(list2-wrapper)
        │     │        ├─ ::view-transition-old(list2-wrapper)
        │     │        └─ ::view-transition-new(list2-wrapper)
        │     └─ ::view-transition-image-pair(lists-wrapper)
        │        ├─ ::view-transition-old(lists-wrapper)
        │        └─ ::view-transition-new(lists-wrapper)
        ├─ div#list1-wrapper
        │  ├─ ul
        │  │  ├─ li#item1
        │  │  ├─ li#item2
        │  │  └─ li#item3
        │  └─ button.reorder
        └─ div#list2-wrapper
           ├─ ul (SCOPE)
           │  ├─ ::view-transition
           │  │  └─ ::view-transition-group(list)
           │  │     ├─ ::view-transition-group-children(list    )
           │  │     │  ├─ ::view-transition-group(item4)
           │  │     │  │  └─ ::view-transition-image-pair(item4)
           │  │     │  │     ├─ ::view-transition-old(item4)
           │  │     │  │     └─ ::view-transition-new(item4)
           │  │     │  ├─ ::view-transition-group(item5)
           │  │     │  │  └─ …
           │  │     │  …
           │  │     └─ ::view-transition-image-pair(list)
           │  │        ├─ ::view-transition-old(list)
           │  │        └─ ::view-transition-new(list)
           │  ├─ li#item4
           │  ├─ li#item5
           │  └─ li#item6
           └─ button.reorder

その他の情報

要素スコープのビュー遷移について詳しくは、説明css-view-transitions-2 仕様オープン仕様の編集リストをご覧ください。