Chrome DevTools で、最上位レイヤ要素のサポートを追加します。これにより、デベロッパーは最上位レイヤ要素を使用するコードをデバッグしやすくなります。
この記事では、最上位レイヤ要素の概要、最上位レイヤ要素を含む DOM 構造を理解してデバッグするために DevTools で最上位レイヤコンテンツを可視化する方法、および DevTools の最上位レイヤサポートの実装方法について説明します。
最上位レイヤと最上位レイヤの要素
<dialog>
をモーダルとして開いたときに、内部で具体的にどのような処理が行われるのでしょうか。🤔
最上位レイヤに配置されます。最上位レイヤのコンテンツは、他のすべてのコンテンツの上に表示されます。たとえば、モーダル ダイアログは他のすべての DOM コンテンツの上に表示される必要があるため、ブラウザは自動的にこの要素を「最上位レイヤ」にレンダリングします。作成者が手動で Z-Index に目を向ける必要はありません。最上位レイヤの要素は、Z-Index が最も高い要素の上に表示されます。
最上位レイヤは、「最上層」と表現できます。各ドキュメントには 1 つのビューポートが関連付けられるため、最上位レイヤも 1 つ関連付けられます。 最上位のレイヤ内に同時に複数の要素を配置できます。こうなると、それらは積み重ねられ、最後の 1 つが一番上になります。つまり、最上位のレイヤ要素はすべて、最上位レイヤの後入れ先出し(LIFO)スタックに配置されます。
<dialog>
要素は、ブラウザが最上位レイヤにレンダリングする唯一の要素ではありません。現在、最上位のレイヤ要素は次のとおりです。
全画面モードのポップオーバー、モーダル ダイアログ、要素。
次のダイアログの実装を確認します。
<main>
<button onclick="window.dialog.showModal();">Open Dialog</button>
</main>
<dialog id="dialog"></dialog>
次のデモでは、背景にスタイルを適用したダイアログをいくつか紹介しています(背景については後述します)。
背景とは
幸い、最上位レイヤ要素の下にあるコンテンツをカスタマイズする方法があります。
最上位レイヤのすべての要素には、背景と呼ばれる CSS 疑似要素があります。
Backdrop は、最上位レイヤ要素のすぐ下にレンダリングされる、ビューポートのサイズのボックスです。::backdrop
疑似要素を使用すると、最上位レイヤの最上位にある要素の下にあるすべての要素を隠したり、スタイル設定したり、完全に非表示にしたりできます。
複数の要素をモーダルにすると、ブラウザは背景をその最前面の要素の直下、および他の全画面表示要素の上に描画します。
背景のスタイルを設定する方法は次のとおりです。
/* The browser displays the backdrop only when the dialog.showModal() function opens the dialog.*/
dialog::backdrop {
background: rgba(255,0,0,.25);
}
最初の背景のみを表示するにはどうすればよいですか?
すべての最上位レイヤ要素には、最上位レイヤ スタックに属する背景があります。これらの背景は互いに重なるように設計されているため、背景の不透明度が 100% でない場合、その下の背景は表示されます。
最上位レイヤ スタックの最初の背景のみを表示する必要がある場合は、最上位レイヤ スタックでアイテム ID を追跡することで、これを実現できます。
追加された要素が最上位レイヤの最初の要素でない場合は、要素が最上位レイヤに配置されたときに呼び出される関数が、hiddenBackdrop
クラスを ::backdrop
に適用します。このクラスは、最上レイヤから要素が削除されると削除されます。
このサンプル デモのコードを確認してください。
DevTools の最上位レイヤのサポート設計
DevTools で最上位レイヤがサポートされているため、デベロッパーは最上位レイヤのコンセプトを理解し、最上位レイヤのコンテンツがどのように変化するかを可視化できます。これらの機能により、デベロッパーは次のことを把握できます。
- 任意の時点の最上位レイヤの要素とその順序。
- 任意の時点でスタックの一番上にある要素。
さらに、DevTools の最上位レイヤのサポートにより、最上位レイヤ スタック内の背景疑似要素の位置を可視化できます。ツリー要素ではありませんが、最上位レイヤの仕組みにおいて重要な役割を果たし、デベロッパーにとって有用です。
最上位レイヤのサポート機能により、次のことが可能になります。
- 最上位レイヤ スタックにある要素を常に監視します。最上位レイヤで要素が追加または削除されると、最上位レイヤの表現スタックが動的に変化します。
- 最上位のレイヤ スタックでの要素の位置を確認します。
- 最上位レイヤの要素からジャンプするツリー内の背景疑似要素から、最上位レイヤ表現コンテナ内の要素または背景疑似要素に転送し、その逆の方向に移動します。
これらの機能の使い方を見ていきましょう。
最上位レイヤのコンテナ
最上位レイヤ要素を可視化できるように、DevTools は要素ツリーに最上位レイヤ コンテナを追加します。これは </html>
終了タグの後に配置されます。
このコンテナを使用すると、最上位レイヤ スタックの要素をいつでもモニタリングできます。最上位レイヤのコンテナは、最上位レイヤの要素とその背景へのリンクのリストです。最上位レイヤで要素が追加または削除されると、最上位レイヤの表現スタックが動的に変化します。
要素ツリー内または最上位レイヤコンテナ内の最上位レイヤ要素を検索するには、最上位レイヤコンテナ内の最上位レイヤ要素表現から、要素ツリー内の同じ要素へのリンクをクリックし、戻ります。
最上位レイヤのコンテナ要素から最上位レイヤのツリー要素にジャンプするには、最上位レイヤコンテナ内の要素の横にある [表示] ボタンをクリックします。
最上位レイヤのツリー要素から最上位のレイヤコンテナ内のリンクにジャンプするには、要素の横にある最上位レイヤバッジをクリックします。
最上位レイヤのバッジを含め、すべてのバッジはオフにできます。バッジを無効にするには、バッジを右クリックして [バッジ設定] を選択し、非表示にするバッジの横にあるチェックボックスをオフにします。
最上位レイヤ スタックでの要素の順序
最上位レイヤのコンテナには、要素がスタックに表示されるときと同じように表示されますが、その順番は逆になります。スタック要素の最上部は、最上位レイヤコンテナの要素リストの最後に配置されます。つまり、最上位レイヤのコンテナリストの最後の要素が、現在ドキュメント内で操作できる要素となります。
ツリー要素の横にあるバッジは、その要素が最上位のレイヤに属していて、スタック内での要素の位置番号を示しています。
このスクリーンショットでは、最上層のスタックは 2 つの要素で構成され、2 番目の要素がスタックの最上部にあります。2 つ目の要素を削除すると、1 つ目の要素が上部に移動します。
最上位レイヤ コンテナ内の背景
前述のように、すべての最上位レイヤ要素には、背景という CSS 疑似要素があります。この要素はスタイルを設定できるため、要素を調べてその表現を確認すると便利です。
要素ツリーでは、背景要素はそれが属する要素の終了タグの前に配置されます。ただし、最上位レイヤのコンテナでは、背景リンクはそれが属する最上位レイヤ要素のすぐ上にリストされます。
DOM ツリーの変更
DevTools で個々の DOM ツリー要素を作成、管理するクラスである ElementsTreeElement
は、最上位レイヤコンテナを実装するには不十分でした。
最上位レイヤ コンテナをツリーのノードとして表示するために、DevTools ツリー要素のノードを作成する新しいクラスを追加しました。以前は、DevTools 要素ツリーを作成するクラスが、すべての TreeElement
を DOMNode
(backendNodeId
とその他のバックエンド関連のプロパティを持つクラス)で初期化していました。次に、backendNodeId
がバックエンドに割り当てられます。
最上位レイヤ要素へのリンクのリストを持つ最上位レイヤのコンテナノードは、通常のツリー要素ノードとして動作する必要があります。ただし、このノードは「実際の」DOM ノードとバックエンドでは、最上位レイヤのコンテナノードを作成する必要はない。
最上位レイヤを表すフロントエンド ノードを作成するために、DOMNode
なしで作成された新しいタイプのフロントエンド ノードを追加しました。この最上位レイヤのコンテナ要素は、DOMNode
を持たない最初のフロントエンド ノードです。つまり、フロントエンドにのみ存在し、バックエンドは認識しません。説明します。他のノードと同じ動作になるように、フロントエンド ノードの動作を担う UI.TreeOutline.TreeElement
クラスを拡張する新しい TopLayerContainer
クラスを作成しました。
目的の配置にするため、要素をレンダリングするクラスは <html>
タグの次の兄弟要素として TopLayerContainer
を追加します。
新しい最上位レイヤのバッジは、要素が最上位レイヤにあることを示し、TopLayerContainer
要素内のこの要素のショートカットへのリンクとして機能します。
初期設計
当初の計画は、要素へのリンクのリストを作成するのではなく、最上位レイヤの要素を最上位レイヤのコンテナに複製することでした。DevTools では要素の子の取得が機能するため、このソリューションは実装しませんでした。各要素には、子の取得に使用される親ポインタがあり、複数のポインタを持つことはできません。そのため、適切に展開してすべての子をツリー内の複数の場所に格納するノードは作成できません。 一般に、システムは重複するサブツリーを念頭に置いてビルドされていません。
私たちがたどった妥協点は、フロントエンドの DOM ノードを複製するのではなく、そのノードへのリンクを作成するというものでした。DevTools の要素へのリンクを作成するクラスは ShortcutTreeElement
で、UI.TreeOutline.TreeElement
を拡張します。ShortcutTreeElement
は、DevTools の他の DOM ツリー要素と同じように動作しますが、バックエンドに対応するノードがなく、ElementsTreeElement
にリンクするボタンを備えています。
最上位レイヤノードに対する各 ShortcutTreeElement
には、DevTools の DOM ツリー内の ::backdrop
疑似要素の表現にリンクする子 ShortcutTreeElement
があります。
初期設計:
Chrome DevTools Protocol(CDP)の変更点
最上位レイヤのサポートを実装するには、Chrome DevTools Protocol(CDP)を変更する必要があります。CDP は、DevTools と Chromium 間の通信プロトコルとして機能します。
以下を追加する必要があります。
- 任意のタイミングでフロントエンドから呼び出すコマンド。
- バックエンド側からフロントエンドでトリガーするイベント。
CDP: DOM.getTopLayerElements
コマンド
現在の最上位レイヤ要素を表示するには、最上位レイヤにある要素のノード ID のリストを返す、新しい試験運用版の CDP コマンドが必要です。DevTools が開かれたとき、または最上位のレイヤ要素が変更されるたびに、DevTools がこのコマンドを呼び出します。コマンドは次のようになります。
# Returns NodeIds of the current top layer elements.
# Top layer renders closest to the user within a viewport, therefore, its elements always
# appear on top of all other content.
experimental command getTopLayerElements
returns
# NodeIds of the top layer elements.
array of NodeId nodeIds
CDP: DOM.topLayerElementsUpdated
イベント
最上位レイヤ要素の最新のリストを取得するには、最上位レイヤ要素が変更されるたびに試験運用版の CDP イベントをトリガーする必要があります。このイベントはフロントエンドに変更を通知します。その後、DOM.getTopLayerElements
コマンドを呼び出して新しい要素リストを受け取ります。
このイベントは次のようになります。
# Called by the change of the top layer elements.
experimental event topLayerElementsUpdated
CDP に関する考慮事項
最上位レイヤの CDP サポートを実装する方法には、複数の選択肢がありました。もう一つ検討したのは、最上位レイヤ要素の追加や削除をフロントエンドに通知するだけではなく、上位レイヤ要素のリストを返すイベントを作成することです。
コマンドの代わりに、topLayerElementAdded
と topLayerElementRemoved
の 2 つのイベントを作成することもできます。この例では、要素を受け取ることになり、フロントエンドの最上位レイヤ要素の配列を管理する必要があります。
現在、フロントエンド イベントは getTopLayerElements
コマンドを呼び出して、更新された要素のリストを取得します。イベントがトリガーされるたびに、変更の原因となった要素のリストや特定の要素のリストを送信すると、コマンドを呼び出すステップを省略できます。
ただし、この場合、フロントエンドはどの要素を push するかの制御を失います。
このように実装したのは、最上位レイヤノードをリクエストするタイミングをフロントエンドで決定する方がよいと考えたためです。たとえば、最上位レイヤが UI で折りたたまれている場合や、ユーザーが要素ツリーのない DevTools パネルを使用している場合は、ツリーの深い階層にある追加のノードを取得する必要はありません。