Chrome DevTools で、最上位レイヤ要素のサポートが追加され、デベロッパーは上位レイヤ要素を利用するコードを簡単にデバッグできるようになりました。
この記事では、最上位レイヤ要素の概要、最上位レイヤの要素を含む DOM 構造を把握してデバッグするために DevTools で最上位レイヤのコンテンツを可視化する方法、そして DevTools の最上位レイヤサポートの実装方法について説明します。
最上位レイヤと最上位レイヤの要素とは
<dialog>
をモーダルとして開くと、内部はどのようになるのでしょうか。🤔
最上位レイヤに配置されます。最上位レイヤのコンテンツは、他のすべてのコンテンツの上にレンダリングされます。たとえば、モーダル ダイアログは他のすべての DOM コンテンツの上に表示する必要があり、ブラウザはこの要素を「最上位レイヤ」に自動的にレンダリングします。作成者が手動で Z-Index と戦う必要はありません。最上位レイヤの要素は、Z-Index が最も高い要素の上に表示されます。
最上位レイヤは、「最高スタック レイヤ」と説明できます。各ドキュメントには 1 つのビューポートが関連付けられているため、最上位レイヤも 1 つあります。最上位レイヤには同時に複数の要素を配置できます。その場合、2 枚のフレームが重なって積み重ねられ、最後の 2 つが一番上になります。つまり、最上位レイヤのすべての要素は、最上位レイヤの後入れ先出し(LIFO)スタックに配置されます。
<dialog>
要素は、ブラウザが最上位レイヤにレンダリングする唯一の要素ではありません。現在、最上位のレイヤ要素は、ポップオーバー、モーダル ダイアログ、全画面モードの要素です。
次のダイアログの実装を確認します。
<main>
<button onclick="window.dialog.showModal();">Open Dialog</button>
</main>
<dialog id="dialog"></dialog>
背景(後述する背景)にスタイルが適用されたダイアログがいくつかあるデモを次に示します。
背景とは
幸いなことに、最上位レイヤ要素の下にあるコンテンツをカスタマイズする方法があります。
最上位レイヤのすべての要素に、背景と呼ばれる CSS 疑似要素があります。
背景はビューポートのサイズのボックスで、最上位レイヤ要素のすぐ下にレンダリングされます。::backdrop
疑似要素を使用すると、その要素が最上位レイヤの最上位である場合に、その要素の下にあるすべてのものを見えにくくしたり、スタイルを設定したり、完全に非表示にしたりできます。
複数の要素をモーダルに設定すると、ブラウザは背景をその最前面の要素の直下と、他の全画面要素の前面に描画します。
背景のスタイルを設定する方法は次のとおりです。
/* The browser displays the backdrop only when the dialog.showModal() function opens the dialog.*/
dialog::backdrop {
background: rgba(255,0,0,.25);
}
最初の背景のみを表示する方法
すべての最上位レイヤ要素には、最上位レイヤ スタックに属する背景があります。これらの背景は互いに重なるように設計されているため、背景の不透明度が 100% でない場合は、その下の背景が表示されます。
最上位レイヤスタック内の最初の背景のみを表示する必要がある場合は、最上位レイヤスタックでアイテム識別子を追跡することで実現できます。
追加された要素が最上位レイヤの最初の要素ではない場合、要素が最上位レイヤに配置されたときに呼び出される関数が hiddenBackdrop
クラスを ::backdrop
に適用します。このクラスは、要素が最上位レイヤから削除されると削除されます。
このサンプルデモのコードを確認してください。
DevTools での最上位レイヤのサポート設計
最上位レイヤに対する DevTools のサポートにより、デベロッパーは最上位レイヤのコンセプトを理解し、最上位レイヤのコンテンツがどのように変化するかを可視化できるようになります。これらの機能は、デベロッパーが次のことを特定するのに役立ちます。
- 任意の時点での最上位レイヤの要素とその順序。
- 任意の時点でスタックの一番上にある要素。
さらに、DevTools の最上位レイヤのサポートにより、最上位レイヤスタック内の背景疑似要素の位置を可視化できます。これはツリー要素ではありませんが、最上位レイヤの仕組みにおいて重要な役割を果たし、デベロッパーにとって有用です。
最上位レイヤのサポート機能を使用すると、次のことができます。
- 最上位レイヤのスタック内にある要素を常に確認できます。最上位レイヤで要素が追加または削除されると、最上位レイヤの表現スタックは動的に変化します。
- 最上位レイヤ スタック内の要素の位置を確認します。
- ツリー内の最上位レイヤ要素または要素の背景擬似要素から、最上位レイヤ表現コンテナ内の要素または背景擬似要素にジャンプして戻ります。
これらの機能の使い方を見ていきましょう。
最上位レイヤのコンテナ
最上位レイヤの要素を可視化するため、DevTools は要素ツリーに最上位レイヤのコンテナを追加します。終了タグ </html>
の後に配置されます。
このコンテナを使用すると、最上位レイヤのスタック内の要素をいつでも監視できます。最上位レイヤのコンテナは、最上位レイヤの要素とその背景へのリンクのリストです。最上位レイヤで要素が追加または削除されると、最上位レイヤの表現スタックは動的に変化します。
要素ツリー内または最上位レイヤコンテナ内の上位レイヤ要素を見つけるには、最上位レイヤコンテナ内の上位レイヤ要素表現から、要素ツリー内の同じ要素へのリンク、およびその逆方向のリンクをクリックします。
最上位レイヤのコンテナ要素から最上位のレイヤツリーの要素にジャンプするには、最上位レイヤのコンテナの要素の横にある [表示] ボタンをクリックします。
最上位レイヤのツリー要素から、最上位レイヤのコンテナ内のリンクに移動するには、要素の横にある最上位レイヤバッジをクリックします。
最上位レイヤのバッジを含め、バッジはオフにできます。バッジを無効にするには、バッジを右クリックして [バッジの設定] を選択し、非表示にするバッジの横にあるチェックボックスをオフにします。
最上位レイヤスタックでの要素の順序
最上位レイヤのコンテナには、要素がスタック内にあるときと同じ順序で表示されます。スタック要素の先頭は、最上位レイヤコンテナの要素リストの最後の要素です。つまり、最上位レイヤのコンテナ リストの最後の要素が、現在ドキュメントで操作できる要素です。
ツリー要素の横にあるバッジは、その要素が最上位レイヤに属し、スタック内の要素の位置番号を含んでいるかどうかを示します。
このスクリーンショットでは、最上位レイヤのスタックは 2 つの要素で構成され、2 番目の要素がスタックの一番上にあります。2 つ目の要素を削除すると、1 つ目の要素が上部に移動します。
最上位レイヤのコンテナの背景
前述のように、すべての最上位レイヤ要素には、背景という CSS 疑似要素があります。この要素はスタイルを設定できるため、検査してその表現を確認することも有用です。
要素ツリーでは、背景要素はそれが属する要素の終了タグの前にあります。ただし、最上位レイヤのコンテナでは、背景リンクはそれが属する最上位のレイヤ要素のすぐ上にリストされます。
DOM ツリーの変更
ElementsTreeElement
(DevTools で個々の DOM ツリー要素の作成と管理を行うクラス)は、最上位レイヤのコンテナを実装するには不十分でした。
最上位レイヤのコンテナをツリーのノードとして表示するために、DevTools のツリー要素ノードを作成する新しいクラスを追加しました。以前は、DevTools 要素ツリーを作成するクラスは、すべての TreeElement
を DOMNode
(backendNodeId
とその他のバックエンド関連プロパティを持つクラス)で初期化していました。次に、backendNodeId
がバックエンドに割り当てられます。
最上位レイヤの要素へのリンクのリストを持つ最上位レイヤのコンテナノードは、通常のツリー要素ノードとして動作する必要があります。ただし、このノードは「実際の」DOM ノードではないため、バックエンドで最上位レイヤのコンテナノードを作成する必要はありません。
最上位レイヤを表すフロントエンド ノードを作成するために、DOMNode
なしで作成される新しいタイプのフロントエンド ノードを追加しました。この最上位レイヤのコンテナ要素は、DOMNode
がない最初のフロントエンド ノードです。つまり、この要素はフロントエンドにのみ存在し、バックエンドはそれを認識しません。他のノードと同じ動作を持たせるために、フロントエンド ノードの動作を担う UI.TreeOutline.TreeElement
クラスを拡張する新しい TopLayerContainer
クラスを作成しました。
必要な配置を実現するため、要素をレンダリングするクラスは、<html>
タグの次の兄弟として TopLayerContainer
を付加します。
新しい最上位レイヤバッジは、要素が最上位レイヤにあることを示し、TopLayerContainer
要素内でこの要素のショートカットへのリンクとして機能します。
初期設計
当初の計画では、要素へのリンクのリストを作成するのではなく、最上位レイヤの要素を最上位レイヤのコンテナに複製することにしました。DevTools での要素の子の取得の仕組みのため、このソリューションは実装しませんでした。各要素には、子の取得に使用される親ポインタがあります。複数のポインタを持つことはできません。したがって、ツリーの複数の場所にすべての子を適切に展開して含まれるノードを用意することはできません。一般に、システムはサブツリーの重複を念頭に置いて構築されていません。
私たちが到達した侵害では、それらのノードを複製するのではなく、フロントエンドの DOM ノードへのリンクを作成していました。DevTools で要素へのリンクを作成するクラスは、UI.TreeOutline.TreeElement
を拡張する ShortcutTreeElement
です。ShortcutTreeElement
は他の DevTools DOM ツリー要素と同じように動作しますが、バックエンドに対応するノードがなく、ElementsTreeElement
にリンクするボタンがあります。最上位レイヤノードへの各 ShortcutTreeElement
には子 ShortcutTreeElement
があり、DevTools DOM ツリーの ::backdrop
疑似要素の表現にリンクしています。
初期設計:
Chrome DevTools プロトコル(CDP)の変更点
最上位レイヤのサポートを実装するには、Chrome DevTools プロトコル(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
コマンドを呼び出して、更新された要素のリストを取得します。イベントがトリガーされるたびに、変更の原因となった要素や特定の要素のリストを送信すれば、コマンドを呼び出す手順を 1 ステップだけ済ませることができます。
ただし、この場合、どの要素を push するかをフロントエンドが制御できなくなります。
このように実装したのは、最上位レイヤのノードをリクエストするタイミングはフロントエンドで決める方がよいと考えたからです。たとえば、最上位レイヤが UI で折りたたまれている場合や、ユーザーが要素ツリーのない DevTools パネルを使用している場合、ツリーの奥深くにある可能性のある追加のノードを取得する必要はありません。