Shadow DOM v1 - 自己完結型のウェブ コンポーネント

ウェブ デベロッパーは Shadow DOM を使用して、ウェブ コンポーネント用に区分化された DOM と CSS を作成可能

概要

Shadow DOM は、ウェブアプリ構築の不安定さを取り除きます。脆弱さ HTML、CSS、JS のグローバルな性質に基づいています。私たちは何年にもわたり、 とてつもない / ツール 問題を回避できる必要がありますたとえば、新しい HTML ID/クラスを使用する場合、 ページで使用されている既存の名前と競合するかどうかがわかりません。 軽い虫がこびりついてくる。 CSS の具体性が大きな問題になる(すべて !important)、スタイル セレクタが制御不能になり パフォーマンスが低下する可能性があります。リスト 説明します。

Shadow DOM は CSS と DOM を修正しますスコープ付きスタイルをウェブに導入します。 できます。ツールや命名規則がない場合は、CSS と マークアップ、実装の詳細の非表示、自己完結型の作成者である コンポーネントを標準 JavaScript で記述しています。

はじめに

Shadow DOM は、次の 3 つのウェブ コンポーネント標準のうちの一つです。 HTML テンプレート Shadow DOMカスタム要素HTML のインポート 以前はリストに含まれていましたが 非推奨になりました。

Shadow DOM を使用するウェブ コンポーネントを作成する必要はありません。ただし、 その利点(CSS スコープ、DOM カプセル化、 構築し、再利用可能なコードをビルドします。 カスタム要素 復元力が高く、高度な構成が可能で、非常に再利用しやすいです。カスタムの場合 要素は(JS API を使用して)新しい HTML を作成する方法であり、Shadow DOM は HTML と CSS を指定する方法です。2 つの API を組み合わせて 実装する方法を学びました

Shadow DOM は、コンポーネント ベースのアプリを構築するためのツールとして設計されています。したがって、 ウェブ開発における一般的な問題に対するソリューションを提供します。

  • 分離された DOM: コンポーネントの DOM は自己完結型です(例: document.querySelector() はコンポーネントの Shadow DOM 内のノードを返しません)。
  • スコープ CSS: Shadow DOM 内で定義された CSS のスコープは、Shadow DOM 内に設定されます。スタイルルール ページのスタイルが浸透していない
  • コンポジション: コンポーネント用に宣言型のマークアップ ベースの API を設計します。
  • CSS の簡素化 - 対象範囲別 DOM とは、シンプルな CSS セレクタなどを使用できる 汎用の ID またはクラス名を使用します。名前の競合を心配する必要はありません。
  • 生産性 - アプリを大きな 1 つの大きな DOM ではなく複数のチャンクとして考える (グローバル)ページをご覧ください。
で確認できます。

fancy-tabs デモ

この記事では、デモ コンポーネント(<fancy-tabs>)について説明します。 コードスニペットを参照できるようになりますブラウザが API をサポートしている場合、 すぐ下にあるライブデモをご覧くださいそれ以外の場合は、GitHub で完全なソースをご確認ください。

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder"> <ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder"></ph> GitHub でソースを表示

Shadow DOM とは

DOM の背景

HTML は扱いやすいため、ウェブを支えています。タグをいくつか宣言することで、 表示と構成の両方を備えたページを数秒で作成できます。ただし、 HTML だけではそれほど役に立ちません。人間にとってテキストは しかし 機械にはもっと何かが必要ですドキュメント オブジェクトを入力してください モデル(DOM)です。

ブラウザはウェブページを読み込むと、さまざまな興味深い処理を行います。次のいずれか 作成者の HTML をライブ ドキュメントに変換します。 基本的に、ページの構造を理解するために、ブラウザは HTML(静的 データモデル(オブジェクト/ノード)に変換します。ブラウザは DOM というノードのツリーを作成して、HTML の階層を作成します。便利な点 DOM の優れた点は、ページがライブで表現されていることです。静的トレーニングの ブラウザにより生成されるノードには、プロパティ、メソッド、 しかも...プログラムで操作できる!だからこそ Google が開発した 要素を直接指定できます。

const header = document.createElement('header');
const h1 = document.createElement('h1');
h1.textContent = 'Hello DOM';
header.appendChild(h1);
document.body.appendChild(header);

このコードは、次の HTML マークアップを生成します。

<body>
    <header>
    <h1>Hello DOM</h1>
    </header>
</body>

それで十分です。その後 Shadow DOM とは何ですか

隠された DOM

Shadow DOM は単なる通常の DOM ですが、2 つの違いがあります。1)作成/使用方法と、 2)ページの他の部分との関係における動作通常 DOM は それらを別の要素の子として追加できます。Shadow DOM を使用すると、 要素に接続され、その要素とは分離されている、スコープが設定された DOM ツリーを作成する 表示されます。このスコープが設定されたサブツリーはシャドウツリーと呼ばれます。要素 シャドウホストが接続されています。シャドウに追加したものは、 <style> を含め、ホスティング要素に対してローカルです。これが Shadow DOM の仕組みです。 CSS スタイルスコープが実現します

Shadow DOM の作成

シャドウルートは、「ホスト」要素にアタッチされるドキュメント フラグメントです。 Shadow ルートをアタッチすると、要素が Shadow DOM を取得します。宛先 要素の Shadow DOM を作成するには、element.attachShadow() を呼び出します。

const header = document.createElement('header');
const shadowRoot = header.attachShadow({mode: 'open'});
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>'; // Could also use appendChild().

// header.shadowRoot === shadowRoot
// shadowRoot.host === header

.innerHTML を使用して Shadow ルートを埋めていますが、他の DOM を使用することもできます APIこれがウェブです。選択肢はあります。

この仕様では要素のリストを定義 作成できません。要素が元の画像に反映される理由はいくつかあります。 次のように指定します。

  • 要素用に独自の内部 Shadow DOM がすでにブラウザによってホストされている (<textarea><input>)。
  • 要素が Shadow DOM(<img>)をホストすることは意味がありません。

たとえば、これは機能しません。

    document.createElement('input').attachShadow({mode: 'open'});
    // Error. `<input>` cannot host shadow dom.

カスタム要素用に Shadow DOM を作成する

Shadow DOM は、特にフロントエンドの カスタム要素。 Shadow DOM を使用して要素の HTML、CSS、JS を区分けすることで、 「ウェブ コンポーネント」を作成します。

- カスタム要素によって Shadow DOM がそれ自体に付加される 次のようにして DOM/CSS をカプセル化します。

// Use custom elements API v1 to register a new HTML tag and define its JS behavior
// using an ES6 class. Every instance of <fancy-tab> will have this same prototype.
customElements.define('fancy-tabs', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    // Attach a shadow root to <fancy-tabs>.
    const shadowRoot = this.attachShadow({mode: 'open'});
    shadowRoot.innerHTML = `
        <style>#tabs { ... }</style> <!-- styles are scoped to fancy-tabs! -->
        <div id="tabs">...</div>
        <div id="panels">...</div>
    `;
    }
    ...
});

ここで興味深い点がいくつかあります。1 つ目は カスタム要素は、<fancy-tabs> のインスタンスがある場合に独自の Shadow DOM を作成します 作成されます。これは constructor() で行います。2 つ目は <style> 内の CSS ルールのスコープは <fancy-tabs> に設定されます。

構成とスロット

構成は Shadow DOM の最も知られていない機能の一つですが、 最も重要な要素です

ウェブ開発の世界では、コンポジションはアプリを構築する方法です。 宣言的に行うことができます。さまざまな構成要素(<div><header><form><input> など)が集まってアプリを形成します。これらのタグの中には 相互に通信します。コンポジションのために、<select><details><form><video> は非常に柔軟です。これらのタグはすべて なんらかの特別な処理を行いますたとえば <select> は、<option><optgroup> をプルダウンにレンダリングする方法を把握しています。 複数選択ウィジェットも用意されています<details> 要素は、<summary> を 展開矢印。<video> も、特定の子に対処する方法を知っています。 <source> 要素はレンダリングされませんが、動画の動作には影響します。 魔法だ!

用語: Light DOM と Shadow DOM

Shadow DOM のコンポジションがウェブに新たな基盤を導入 必要があります。詳細に入る前に 同じ専門用語を使用します

Light DOM

コンポーネントのユーザーが記述するマークアップ。この DOM は コンポーネントの Shadow DOM が作成されます。これは要素の実際の子です。

<better-button>
    <!-- the image and span are better-button's light DOM -->
    <img src="gear.svg" slot="icon">
    <span>Settings</span>
</better-button>

Shadow DOM

コンポーネント作成者が作成する DOM。Shadow DOM はコンポーネントのローカルにあり、 内部構造とスコープ CSS を定義し、実装をカプセル化します。 表示されます。また、コンシューマが作成したマークアップをレンダリングする方法も定義できます。 必要があります。

#shadow-root
    <style>...</style>
    <slot name="icon"></slot>
    <span id="wrapper">
    <slot>Button</slot>
    </span>

フラット化された DOM ツリー

ブラウザがユーザーの Light DOM を Shadow に分散した結果 最終的な成果物がレンダリングされますフラット化されたツリーは、最終的に ページに表示される内容を確認します

<better-button>
    #shadow-root
    <style>...</style>
    <slot name="icon">
        <img src="gear.svg" slot="icon">
    </slot>
    <span id="wrapper">
        <slot>
        <span>Settings</span>
        </slot>
    </span>
</better-button>

<slot>要素

Shadow DOM は、<slot> 要素を使用してさまざまな DOM ツリーをまとめて構成します。 スロットはコンポーネント内のプレースホルダで、ユーザーが情報を入力して マークアップを追加します。1 つ以上のスロットを定義することで、外部のマークアップをレンダリングするよう促すことができる それをコンポーネントの Shadow DOM 内で呼び出します。基本的には、「ユーザーのリクエストを マークアップを追加します。

要素は「クロス」できます<slot> が招待したときの Shadow DOM の境界 できます。これらの要素は分散ノードと呼ばれます。概念的には、 少し奇妙に思えるかもしれません。スロットは DOM を物理的に移動しません。 Shadow DOM 内の別の場所でレンダリングします。

コンポーネントは、その Shadow DOM に 0 個以上のスロットを定義できます。スロットは空でもかまいません フォールバックコンテンツの提供もできます。ユーザーが Light DOM を提供していない場合 代替コンテンツがスロットにレンダリングされます。

<!-- Default slot. If there's more than one default slot, the first is used. -->
<slot></slot>

<slot>fallback content</slot> <!-- default slot with fallback content -->

<slot> <!-- default slot entire DOM tree as fallback -->
    <h2>Title</h2>
    <summary>Description text</summary>
</slot>

名前付きスロットを作成することもできます。名前付きスロットは ユーザーが名前で参照する Shadow DOM です。

- <fancy-tabs> の Shadow DOM 内のスロット:

#shadow-root
    <div id="tabs">
    <slot id="tabsSlot" name="title"></slot> <!-- named slot -->
    </div>
    <div id="panels">
    <slot id="panelsSlot"></slot>
    </div>

コンポーネント ユーザーは、次のように <fancy-tabs> を宣言します。

<fancy-tabs>
    <button slot="title">Title</button>
    <button slot="title" selected>Title 2</button>
    <button slot="title">Title 3</button>
    <section>content panel 1</section>
    <section>content panel 2</section>
    <section>content panel 3</section>
</fancy-tabs>

<!-- Using <h2>'s and changing the ordering would also work! -->
<fancy-tabs>
    <h2 slot="title">Title</h2>
    <section>content panel 1</section>
    <h2 slot="title" selected>Title 2</h2>
    <section>content panel 2</section>
    <h2 slot="title">Title 3</h2>
    <section>content panel 3</section>
</fancy-tabs>

このフラット化されたツリーは次のようになります。

<fancy-tabs>
    #shadow-root
    <div id="tabs">
        <slot id="tabsSlot" name="title">
        <button slot="title">Title</button>
        <button slot="title" selected>Title 2</button>
        <button slot="title">Title 3</button>
        </slot>
    </div>
    <div id="panels">
        <slot id="panelsSlot">
        <section>content panel 1</section>
        <section>content panel 2</section>
        <section>content panel 3</section>
        </slot>
    </div>
</fancy-tabs>

このコンポーネントはさまざまな構成を処理できますが、 フラット化された DOM ツリーに変更はありません。<button> から <h2>。このコンポーネントは、さまざまな種類の子を処理するために作成されたものです。 <select> のように!

スタイル設定

ウェブ コンポーネントのスタイル設定にはさまざまなオプションがあります。Shadow を使用するコンポーネント DOM は、メインページでスタイルを設定することも、独自のスタイルを定義することも、 CSS カスタム プロパティの形式)を使用して、ユーザーがデフォルトをオーバーライドできるようにします。

コンポーネント定義のスタイル

Shadow DOM の最も便利な機能は、スコープ CSS です。

  • 外側のページの CSS セレクタは、コンポーネント内では適用されません。
  • 内部で定義したスタイルは染み込まない。スコープはホスト要素です。

Shadow DOM 内で使用される CSS セレクタは、コンポーネントにローカルに適用されます。イン つまり、共通の ID/クラス名を再度使用しても、心配せずに済みます。 詳細を確認できますCSS セレクタをシンプルにすることを推奨 確認できますパフォーマンスの面でも優れています。

- Shadow ルートで定義されたスタイルがローカルなものである

#shadow-root
    <style>
    #panels {
        box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
        background: white;
        ...
    }
    #tabs {
        display: inline-flex;
        ...
    }
    </style>
    <div id="tabs">
    ...
    </div>
    <div id="panels">
    ...
    </div>

また、スタイルシートのスコープは Shadow ツリーに設定されます。

#shadow-root
    <link rel="stylesheet" href="styles.css">
    <div id="tabs">
    ...
    </div>
    <div id="panels">
    ...
    </div>

<select> 要素がどのように複数選択ウィジェットをレンダリングするか、 プルダウンなど)を使用して、multiple 属性を追加します。

<select multiple>
  <option>Do</option>
  <option selected>Re</option>
  <option>Mi</option>
  <option>Fa</option>
  <option>So</option>
</select>

<select> は、指定された属性に基づいて、それ自体を異なるスタイルに設定できます。 宣言します。ウェブ コンポーネントでも、:host を使用してそれ自体のスタイルを設定できます。 選択します。

- それ自体のスタイル設定コンポーネント

<style>
:host {
    display: block; /* by default, custom elements are display: inline */
    contain: content; /* CSS containment FTW. */
}
</style>

:host の問題点の一つは、親ページのルールの方が特異性が高いことです。 :host ルールより優先されます。つまり、外側のスタイルが優先されます。この を使用すると、ユーザーは外部から最上位のスタイル設定をオーバーライドできます。また、:host シャドウルートのコンテキストでしか機能しないため、 説明します

:host(<selector>) の機能的な形式を使用すると、次の場合にホストをターゲットにできます。 <selector> と一致する。このようにすることで、コンポーネントをカプセル化し、 ユーザーの操作や状態に反応する動作や、内部ノードに基づいて できます。

<style>
:host {
    opacity: 0.4;
    will-change: opacity;
    transition: opacity 300ms ease-in-out;
}
:host(:hover) {
    opacity: 1;
}
:host([disabled]) { /* style when host has disabled attribute. */
    background: grey;
    pointer-events: none;
    opacity: 0.4;
}
:host(.blue) {
    color: blue; /* color host when it has class="blue" */
}
:host(.pink) > #tabs {
    color: pink; /* color internal #tabs node when host has class="pink". */
}
</style>

コンテキストに基づくスタイル設定

:host-context(<selector>) は、自身またはその祖先のいずれかである場合、コンポーネントに一致します。 <selector> と一致します。一般的な用途は、コンポーネントの 学習します。たとえば、多くのユーザーはクラスを適用することでテーマ設定を行います。 <html> または <body>:

<body class="darktheme">
    <fancy-tabs>
    ...
    </fancy-tabs>
</body>

:host-context(.darktheme) は、子孫である場合に <fancy-tabs> のスタイルを設定します /.darktheme:

:host-context(.darktheme) {
    color: white;
    background: black;
}

:host-context() はテーマ設定に役立ちますが、より優れたアプローチは、 CSS カスタム プロパティを使用してスタイルフックを作成するをご覧ください。

分散ノードのスタイル設定

::slotted(<compound-selector>) は、特定のゾーンに分散されたノードに一致します。 <slot>

名バッジ コンポーネントを作成したとします。

<name-badge>
    <h2>Eric Bidelman</h2>
    <span class="title">
    Digital Jedi, <span class="company">Google</span>
    </span>
</name-badge>

コンポーネントの Shadow DOM では、ユーザーの <h2>.title のスタイルを設定できます。

<style>
::slotted(h2) {
    margin: 0;
    font-weight: 300;
    color: red;
}
::slotted(.title) {
    color: orange;
}
/* DOESN'T WORK (can only select top-level nodes).
::slotted(.company),
::slotted(.title .company) {
    text-transform: uppercase;
}
*/
</style>
<slot></slot>

前述したように、<slot> はユーザーの Light DOM を移動しません。日時 ノードが <slot> に分散されている場合、<slot> は DOM をレンダリングしますが、 ノードは物理的に置かれたままになります配信前に適用したスタイルは、引き続き 配布後に適用されます。ただし、Light DOM が分散されている場合は、 追加のスタイル(Shadow DOM で定義されたスタイル)を使用できます。

<fancy-tabs> のより詳細な例をもう 1 つ示します。

const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
    <style>
    #panels {
        box-shadow: 0 2px 2px rgba(0, 0, 0, .3);
        background: white;
        border-radius: 3px;
        padding: 16px;
        height: 250px;
        overflow: auto;
    }
    #tabs {
        display: inline-flex;
        -webkit-user-select: none;
        user-select: none;
    }
    #tabsSlot::slotted(*) {
        font: 400 16px/22px 'Roboto';
        padding: 16px 8px;
        ...
    }
    #tabsSlot::slotted([aria-selected="true"]) {
        font-weight: 600;
        background: white;
        box-shadow: none;
    }
    #panelsSlot::slotted([aria-hidden="true"]) {
        display: none;
    }
    </style>
    <div id="tabs">
    <slot id="tabsSlot" name="title"></slot>
    </div>
    <div id="panels">
    <slot id="panelsSlot"></slot>
    </div>
`;

この例では、タブタイトル用の名前付きスロットと、 タブパネルのコンテンツスロットですユーザーがタブを選択すると、選択内容が太字で表示されます。 そのパネルを表示します。そのために、内部 IP アドレスしか持たない分散ノードを selected 属性。カスタム要素の JS(ここには示されていません)は、 属性を適切な時刻に指定します。

外部からコンポーネントのスタイル設定を行う

外部からコンポーネントのスタイルを設定するには、いくつかの方法があります。最も簡単な タグ名をセレクタとして使用します。

fancy-tabs {
    width: 500px;
    color: red; /* Note: inheritable CSS properties pierce the shadow DOM boundary. */
}
fancy-tabs:hover {
    box-shadow: 0 3px 3px #ccc;
}

外側のスタイルは常に Shadow DOM で定義されたスタイルよりも優先されます。たとえば ユーザーがセレクタ fancy-tabs { width: 500px; } を記述した場合は、このセレクタが優先されます。 コンポーネントのルール: :host { width: 650px;}

コンポーネント自体のスタイルを設定しても、ここまではやっと終わりです。では、 コンポーネントの内部をスタイル設定するにはどうすればよいでしょうか。そのためには、カスタム CSS が必要です。 プロパティをご覧ください。

CSS カスタム プロパティを使用してスタイルフックを作成する

コンポーネント作成者がスタイルフックを提供している場合、ユーザーは内部スタイルを調整できます。 CSS カスタム プロパティを使用します。概念的には、この考え方は <slot>。「スタイル プレースホルダ」を作成するオーバーライドできます

- <fancy-tabs> を使用すると、ユーザーは背景色をオーバーライドできます。

<!-- main page -->
<style>
    fancy-tabs {
    margin-bottom: 32px;
    --fancy-tabs-bg: black;
    }
</style>
<fancy-tabs background>...</fancy-tabs>

Shadow DOM の内部:

:host([background]) {
    background: var(--fancy-tabs-bg, #9E9E9E);
    border-radius: 10px;
    padding: 10px;
}

この場合、コンポーネントはバックグラウンドの値として black を使用します。これは、 ユーザーから提供された情報です。それ以外の場合は、デフォルトの #9E9E9E が使用されます。

高度なトピック

クローズド シャドウルートの作成(非推奨)

Shadow DOM には、「クローズド」と呼ばれる別の手法もあります。モードです。新しい 閉じられた Shadow ツリー。JavaScript の外部からは内部 DOM にアクセスできない 必要があります。これは、<video> などのネイティブ要素の仕組みに似ています。 JavaScript は <video> の Shadow DOM にアクセスできません。これは、ブラウザが クローズド モードのシャドウルートを使用して実装します。

- クローズド シャドウ ツリーの作成:

const div = document.createElement('div');
const shadowRoot = div.attachShadow({mode: 'closed'}); // close shadow tree
// div.shadowRoot === null
// shadowRoot.host === div

他の API もクローズド モードの影響を受けます。

  • Element.assignedSlot / TextNode.assignedSlotnull が返されます
  • Event.composedPath(): シャドウ内の要素に関連付けられたイベント DOM は [] を返します。
で確認できます。

ウェブ コンポーネントを作成してはならない理由を、 {mode: 'closed'}:

  1. 人為的なセキュリティ感覚。攻撃者によるネットワーク侵入を Element.prototype.attachShadow のハイジャック。

  2. クローズド モードでは、カスタム要素コードが自身の要素にアクセスできない Shadow DOM のものです。これで全然不合格です。代わりに、参照ファイルではなく 後で使用したい場合は、querySelector() などを使用してください。これは完全に はクローズド モードの本来の目的を果たせません。

        customElements.define('x-element', class extends HTMLElement {
        constructor() {
        super(); // always call super() first in the constructor.
        this._shadowRoot = this.attachShadow({mode: 'closed'});
        this._shadowRoot.innerHTML = '<div class="wrapper"></div>';
        }
        connectedCallback() {
        // When creating closed shadow trees, you'll need to stash the shadow root
        // for later if you want to use it again. Kinda pointless.
        const wrapper = this._shadowRoot.querySelector('.wrapper');
        }
        ...
    });
    
  3. クローズド モードでは、エンドユーザーに対するコンポーネントの柔軟性が低下します。皆さんが ウェブ コンポーネントを構築すると、アプリのコンポーネントを 機能。構成オプション。ユーザーが求めるユースケース。共通 内部ノードに適したスタイル設定のフックが忘れられている場合です。 クローズド モードでは、ユーザーがデフォルトをオーバーライドして微調整する方法はない あります。コンポーネントの内部にアクセスできることは、非常に便利です。 最終的に、ユーザーはコンポーネントをフォークするか、別のコンポーネントを 想定していた動作が得られない場合はお手数をおかけしますが、よろしくお願いいたします。

JS でのスロットの操作

Shadow DOM API には、スロットや分散処理のためのユーティリティが 説明します。これらは、カスタム要素を作成するときに便利です。

スロット変更イベント

slotchange イベントは、スロットの分散ノードが変更されると起動します。対象 たとえば、ユーザーが Light DOM から子を追加または削除した場合です。

const slot = this.shadowRoot.querySelector('#slot');
slot.addEventListener('slotchange', e => {
    console.log('light dom children changed!');
});

Light DOM のその他の種類の変更をモニタリングするには、 MutationObserver を要素のコンストラクタ内で指定する必要があります。

スロットでレンダリングされている要素

スロットに関連付けられている要素を知っておくと便利な場合があります。発信 slot.assignedNodes(): スロットがレンダリングする要素を確認します。「 {flatten: true} オプションもスロットのフォールバック コンテンツを返します(ノードがない場合、 されます)。

たとえば、次のような Shadow DOM があるとします。

<slot><b>fallback content</b></slot>
用途電話結果
<my-component>コンポーネント テキスト</my-component> slot.assignedNodes(); [component text]
&lt;my-component&gt;&lt;/my-component&gt; slot.assignedNodes(); []
&lt;my-component&gt;&lt;/my-component&gt; slot.assignedNodes({flatten: true}); [<b>fallback content</b>]

要素が割り当てられているスロット

逆の質問に答えることもできます。element.assignedSlot の伝え方 どのコンポーネント スロットに要素が割り当てられているかを示すことができます。

Shadow DOM イベントモデル

イベントが Shadow DOM から出現すると、そのターゲットが Shadow DOM の カプセル化を実現します。つまり イベントのターゲットを絞り込んで 内部要素ではなくコンポーネントに由来するように 説明します一部のイベントは Shadow DOM の外部に伝播しません。

シャドウ境界を越えるイベントは次のとおりです。

  • 主なイベント: blurfocusfocusinfocusout
  • マウスイベント: clickdblclickmousedownmouseentermousemove など
  • ホイール イベント: wheel
  • 入力イベント: beforeinputinput
  • キーボード イベント: keydownkeyup
  • 楽曲イベント: compositionstartcompositionupdatecompositionend
  • DragEvent: dragstartdragdragenddrop など

ヒント

シャドウツリーが開いている場合、event.composedPath() を呼び出すと配列が返されます。 通過するノードの数を表します。

カスタム イベントを使用する

Shadow ツリーの内部ノードで呼び出されるカスタム DOM イベントは シャドーイングは、 composed: true フラグ:

// Inside <fancy-tab> custom element class definition:
selectTab() {
    const tabs = this.shadowRoot.querySelector('#tabs');
    tabs.dispatchEvent(new Event('tab-select', {bubbles: true, composed: true}));
}

composed: false(デフォルト)の場合、コンシューマはイベントをリッスンできません 作成します。

<fancy-tabs></fancy-tabs>
<script>
    const tabs = document.querySelector('fancy-tabs');
    tabs.addEventListener('tab-select', e => {
    // won't fire if `tab-select` wasn't created with `composed: true`.
    });
</script>

フォーカスの処理

Shadow DOM のイベントモデルで説明したように、 Shadow DOM の内部が、ホスティング要素から来たように調整されます。 たとえば、シャドウルート内の <input> をクリックするとします。

<x-focus>
    #shadow-root
    <input type="text" placeholder="Input inside shadow dom">

focusのイベントの発生元は<input>ではなく、<x-focus>になります。 同様に、document.activeElement<x-focus> になります。シャドウルートが mode:'open' で作成されたもの(クローズド モードを参照)は、 内部ノードにアクセスできます。

document.activeElement.shadowRoot.activeElement // only works with open mode.

複数のレベルの Shadow DOM が存在している場合( 追加するには、シャドウルートを再帰的にドリルダウンして activeElement を見つけます。

function deepActiveElement() {
    let a = document.activeElement;
    while (a && a.shadowRoot && a.shadowRoot.activeElement) {
    a = a.shadowRoot.activeElement;
    }
    return a;
}

もう 1 つのフォーカス オプションは、delegatesFocus: true オプションです。これは、 Shadow ツリー内の要素のフォーカス動作は次のようになります。

  • Shadow DOM 内のノードをクリックし、そのノードがフォーカス可能な領域でない場合、 最初のフォーカス対象領域がフォーカスされます。
  • Shadow DOM 内のノードがフォーカスされると、:focus が 追加することもできます。

- delegatesFocus: true がどのようにフォーカス動作を変更するか

<style>
    :focus {
    outline: 2px solid red;
    }
</style>

<x-focus></x-focus>

<script>
customElements.define('x-focus', class extends HTMLElement {
    constructor() {
    super(); // always call super() first in the constructor.

    const root = this.attachShadow({mode: 'open', delegatesFocus: true});
    root.innerHTML = `
        <style>
        :host {
            display: flex;
            border: 1px dotted black;
            padding: 16px;
        }
        :focus {
            outline: 2px solid blue;
        }
        </style>
        <div>Clickable Shadow DOM text</div>
        <input type="text" placeholder="Input inside shadow dom">`;

    // Know the focused element inside shadow DOM:
    this.addEventListener('focus', function(e) {
        console.log('Active element (inside shadow dom):',
                    this.shadowRoot.activeElement);
    });
    }
});
</script>

結果

delegatesFocus: 実際の動作です。

上の図は、<x-focus> がフォーカスされている場合(ユーザーがクリック、タブで開いた状態、 focus() など)、"クリック可能な Shadow DOM テキスト"クリックされるか、内部広告が <input> がフォーカスされています(autofocus を含む)。

delegatesFocus: false を設定した場合は、代わりに次のように表示されます。

<ph type="x-smartling-placeholder">
</ph> delegatesFocus: false で、内部入力はフォーカスされています。 <ph type="x-smartling-placeholder">
</ph> delegatesFocus: false と内部の <input> がフォーカスされている。
で確認できます。
<ph type="x-smartling-placeholder">
</ph> delegatesFocus: false と x-focus
    フォーカスを取得する(tabindex=&#39;0&#39; など)。 <ph type="x-smartling-placeholder">
</ph> delegatesFocus: false<x-focus> フォーカスを取得する(例: tabindex="0")。
<ph type="x-smartling-placeholder">
</ph> delegatesFocus: false と「クリック可能な Shadow DOM テキスト」
    (または、要素の Shadow DOM 内の他の空白部分がクリックされたとき)。 <ph type="x-smartling-placeholder">
</ph> delegatesFocus: false と「クリック可能な Shadow DOM テキスト」 (または、要素の Shadow DOM 内の他の空白部分がクリックされたとき)。

ヒントとアドバイス

私はこの数年間、ウェブ コンポーネントの作成について 1 つか 2 つ学びました。私は コンポーネントやコードの作成に役立つヒントを Shadow DOM のデバッグです。

CSS の包含を使用

通常、ウェブ コンポーネントのレイアウト、スタイル、ペイントはかなり自己完結型です。使用 パフォーマンスのための :host での CSS 包含 勝ち:

<style>
:host {
    display: block;
    contain: content; /* Boom. CSS containment FTW. */
}
</style>

継承可能なスタイルのリセット

継承可能なスタイル(backgroundcolorfontline-height など)は継続されます Shadow DOM 内に継承されます。すなわち、境界を越えて Shadow DOM の境界を できます。新しい状態から始める場合は、all: initial; を使用してリセットします。 継承可能なスタイルが Shadow の境界を越えるときに、初期値にそのスタイルが適用されます。

<style>
    div {
    padding: 10px;
    background: red;
    font-size: 25px;
    text-transform: uppercase;
    color: white;
    }
</style>

<div>
    <p>I'm outside the element (big/white)</p>
    <my-element>Light DOM content is also affected.</my-element>
    <p>I'm outside the element (big/white)</p>
</div>

<script>
const el = document.querySelector('my-element');
el.attachShadow({mode: 'open'}).innerHTML = `
    <style>
    :host {
        all: initial; /* 1st rule so subsequent properties are reset. */
        display: block;
        background: white;
    }
    </style>
    <p>my-element: all CSS properties are reset to their
        initial value using <code>all: initial</code>.</p>
    <slot></slot>
`;
</script>

ページで使用されているすべてのカスタム要素を検索する

ページで使用されているカスタム要素を見つけると便利な場合があります。そのためには、 ページで使用されているすべての要素の Shadow DOM を再帰的に走査する必要がある。

const allCustomElements = [];

function isCustomElement(el) {
    const isAttr = el.getAttribute('is');
    // Check for <super-button> and <button is="super-button">.
    return el.localName.includes('-') || isAttr && isAttr.includes('-');
}

function findAllCustomElements(nodes) {
    for (let i = 0, el; el = nodes[i]; ++i) {
    if (isCustomElement(el)) {
        allCustomElements.push(el);
    }
    // If the element has shadow DOM, dig deeper.
    if (el.shadowRoot) {
        findAllCustomElements(el.shadowRoot.querySelectorAll('*'));
    }
    }
}

findAllCustomElements(document.querySelectorAll('*'));

<template> からの要素の作成

.innerHTML を使用してシャドウルートを設定する代わりに、宣言型の <template>。テンプレートは、インフラストラクチャの構造を宣言するための 作成します

例をご覧ください。 カスタム要素: 再利用可能なウェブ コンポーネントの構築

歴史とブラウザ サポート

ここ数年ウェブ コンポーネントをフォローしている方は、 Chrome 35 以降(Opera)には古いバージョンの Shadow DOM が 説明します。Blink は、引き続き一部の環境において両方のバージョンを並行してサポートします あります。v0 仕様では、シャドウルートを作成する別の方法が提供されていました。 (v1 の element.attachShadow ではなく element.createShadowRoot)。呼び出しの 古い方法では引き続き v0 セマンティクスでシャドウルートが作成されるため、既存の v0 コードが破損することはありません

以前の v0 仕様に興味がある場合は、html5rocks をご覧ください。 記事: 1 2 3. P-MAX と Shadow DOM v0 と v1 の違いをご確認ください。

ブラウザ サポート

Shadow DOM v1 は Chrome 53 でリリースされ(ステータス)、 Opera 40、Safari 10、Firefox 63 です。エッジ 開発を開始しました

Shadow DOM の機能検出を検出するには、attachShadow が存在するかどうかを確認します。

const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;

ポリフィル

ブラウザ サポートが広く利用可能になるまで、 shadydomshadycss ポリフィルを使用して v1 を指定します。 機能。Shady DOM は、Shadow DOM と shadycss のポリフィルの DOM スコープを模倣しています。 CSS カスタム プロパティとネイティブ API が提供するスタイル スコープ。

ポリフィルをインストールします。

bower install --save webcomponents/shadydom
bower install --save webcomponents/shadycss

ポリフィルを使用します。

function loadScript(src) {
    return new Promise(function(resolve, reject) {
    const script = document.createElement('script');
    script.async = true;
    script.src = src;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
    });
}

// Lazy load the polyfill if necessary.
if (!supportsShadowDOMV1) {
    loadScript('/bower_components/shadydom/shadydom.min.js')
    .then(e => loadScript('/bower_components/shadycss/shadycss.min.js'))
    .then(e => {
        // Polyfills loaded.
    });
} else {
    // Native shadow dom v1 support. Go to go!
}

https://github.com/webcomponents/shadycss#usage をご覧ください。 をご覧ください。

まとめ

初めて、適切な CSS スコープを設定する API プリミティブを利用できるようになりました。 DOM スコープ設定が適用されていて、真のコンポジションを持つ。他のウェブ コンポーネント API と組み合わせる Shadow DOM は、カスタム要素と同様に、完全にカプセル化された ハッキングや <iframe> などの古いバッグの使用なしでコンポーネントを使用できます。

ただし、Shadow DOM は本当に複雑です。でも本当に 学びます。少し時間を取ってください。使い方を学んで、質問しましょう。

関連情報

よくある質問

現在、Shadow DOM v1 を使用できますか?

ポリフィルを使えば可能です。ブラウザのサポートをご覧ください。

Shadow DOM にはどのようなセキュリティ機能がありますか。

Shadow DOM はセキュリティ機能ではありません。CSS のスコープ設定に役立つ軽量のツールで、 コンポーネント内の DOM ツリーを隠すことです真のセキュリティ境界が必要な場合は <iframe> を使用する。

ウェブ コンポーネントでは Shadow DOM を使用する必要がありますか?

いいえ。Shadow DOM を使用するウェブ コンポーネントを作成する必要はありません。ただし、 Shadow DOM を使用したカスタム要素を作成することで、 CSS スコープ、DOM カプセル化、コンポジションなどの機能が活用されています。

開いている Shadow ルートと閉じた Shadow ルートの違いは何ですか?

クローズド シャドウルートをご覧ください。