Shadow DOM の操作
Lit コンポーネントは、DOM をカプセル化するために Shadow DOM を使用します。Shadow DOM は、要素に別個の分離されたカプセル化された DOM ツリーを追加する方法を提供します。DOM カプセル化は、ページ上で機能する他のWebコンポーネントやLitコンポーネントを含む、他のコードとの相互運用性を解き放つための鍵です。
Shadow DOM は3つの利点をもたらします。
- DOM スコープ。
document.querySelector
のような DOM API は、コンポーネントの Shadow DOM 内の要素を見つけられないため、グローバルスクリプトが誤ってコンポーネントを破壊する可能性が低くなります。 - スタイルスコープ。残りの DOM ツリーに影響を与えない、Shadow DOM 用のカプセル化されたスタイルを作成できます。
- コンポジション。内部 DOM を含むコンポーネントのシャドウルートは、コンポーネントの子要素とは別に存在します。子要素をコンポーネントの内部 DOM にどのようにレンダリングするかを選択できます。
Shadow DOM の詳細については
- Shadow DOM v1: 自己完結型 Web コンポーネント(Web Fundamentals)
- Shadow DOM の使用(MDN)
古いブラウザ。ネイティブの Shadow DOM が利用できない古いブラウザでは、Web コンポーネントポリフィル を使用できます。Lit の polyfill-support
モジュールは、Web コンポーネントポリフィルと共にロードする必要があることに注意してください。詳細については、「レガシーブラウザの要件」を参照してください。
Shadow DOM 内のノードへのアクセス
「Shadow DOM 内のノードへのアクセス」へのパーマリンクLit は、デフォルトでシャドウルートである renderRoot
にコンポーネントをレンダリングします。内部要素を見つけるには、this.renderRoot.querySelector()
などの DOM クエリ API を使用できます。
renderRoot
は、常にシャドウルートまたは要素のいずれかでなければなりません。これらは、.querySelectorAll()
や .children
などの API を共有します。
コンポーネントの初回レンダリング後(たとえば、firstUpdated
内で)に内部 DOM をクエリするか、ゲッターパターンを使用できます。
firstUpdated() {
this.staticNode = this.renderRoot.querySelector('#static-node');
}
get _closeButton() {
return this.renderRoot.querySelector('#close-button');
}
LitElement は、このようなゲッターを定義するための簡潔な方法を提供する一連のデコレーターを提供します。
@query、@queryAll、および @queryAsync デコレーター
「@query、@queryAll、および @queryAsync デコレーター」へのパーマリンク@query
、@queryAll
、および @queryAsync
デコレーターはすべて、内部コンポーネント DOM 内のノードにアクセスするための便利な方法を提供します。
デコレーターの使用。デコレーターは提案されている JavaScript の機能であるため、デコレーターを使用するには、Babel や TypeScript などのコンパイラを使用する必要があります。詳細については、「デコレーターの使用」を参照してください。
@query
「@query」へのパーマリンククラスプロパティを変更し、レンダリングルートからノードを返すゲッターに変換します。オプションの2番目の引数が true の場合、DOM クエリは一度だけ実行され、結果がキャッシュされます。これは、クエリ対象のノードが変更されない場合のパフォーマンス最適化として使用できます。
import {LitElement, html} from 'lit';
import {query} from 'lit/decorators/query.js';
class MyElement extends LitElement {
@query('#first')
_first;
render() {
return html`
<div id="first"></div>
<div id="second"></div>
`;
}
}
このデコレーターは、次のものと同等です。
get _first() {
return this.renderRoot?.querySelector('#first') ?? null;
}
@queryAll
「@queryAll」へのパーマリンク単一のノードではなく、すべてのノードを返すことを除いて、query
と同じです。querySelectorAll
を呼び出すことと同等です。
import {LitElement, html} from 'lit';
import {queryAll} from 'lit/decorators/queryAll.js';
class MyElement extends LitElement {
@queryAll('div')
_divs;
render() {
return html`
<div id="first"></div>
<div id="second"></div>
`;
}
}
ここでは、_divs
はテンプレート内の両方の <div>
要素を返します。TypeScript の場合、@queryAll
プロパティの型は NodeListOf<HTMLElement>
です。取得するノードの種類が正確にわかっている場合は、より具体的な型にすることができます。
@queryAll('button')
_buttons!: NodeListOf<HTMLButtonElement>
buttons
の後の感嘆符(!
)は、TypeScript の非nullアサーション演算子です。コンパイラに、buttons
は常に定義されており、null
でも undefined
でもないことを伝えます。
@queryAsync
「@queryAsync」へのパーマリンク@query
と似ていますが、ノードを直接返す代わりに、保留中の要素のレンダリングが完了した後にそのノードに解決される Promise
を返します。コードはこれを使用して、updateComplete
プロミスを待つ代わりに使用できます。
これは、たとえば、@queryAsync
によって返されるノードが、別のプロパティの変更の結果として変更される可能性がある場合に役立ちます。
スロットを使用した子要素のレンダリング
「スロットを使用した子要素のレンダリング」へのパーマリンクコンポーネントは子要素を受け入れる場合があります(<ul>
要素が <li>
子要素を持つことができるように)。
<my-element>
<p>A child</p>
</my-element>
デフォルトでは、要素にシャドウツリーがある場合、その子要素はまったくレンダリングされません。
子要素をレンダリングするには、テンプレートに1つ以上の<slot>
要素を含める必要があります。これは、子ノードのプレースホルダーとして機能します。
slot 要素の使用
「slot 要素の使用」へのパーマリンク要素の子要素をレンダリングするには、要素のテンプレートにそれらのための <slot>
を作成します。子要素は DOM ツリー内で移動されませんが、<slot>
の子要素であるかのようにレンダリングされます。たとえば
名前付きスロットの使用
「名前付きスロットの使用」へのパーマリンク子要素を特定のスロットに割り当てるには、子要素の slot
属性がスロットの name
属性と一致するようにします。
名前付きスロットは、一致する
slot
属性を持つ子要素のみを受け入れます。たとえば、
<slot name="one"></slot>
は、属性slot="one"
を持つ子要素のみを受け入れます。slot
属性を持つ子要素は、一致するname
属性を持つスロットにのみレンダリングされます。たとえば、
<p slot="one">...</p>
は<slot name="one"></slot>
にのみ配置されます。
スロットのフォールバックコンテンツの指定
「スロットのフォールバックコンテンツの指定」へのパーマリンクスロットのフォールバックコンテンツを指定できます。フォールバックコンテンツは、子要素がスロットに割り当てられていない場合に表示されます。
<slot>I am fallback content</slot>
フォールバックコンテンツのレンダリング。子ノードがスロットに割り当てられている場合、そのフォールバックコンテンツはレンダリングされません。名前のないデフォルトのスロットは、すべての子ノードを受け入れます。たとえば、<example-element> </example-element>
のように、割り当てられたノードが空白を含むテキストノードのみである場合でも、フォールバックコンテンツはレンダリングされません。カスタム要素の子要素として Lit 式を使用する場合は、スロットのフォールバックコンテンツがレンダリングされるように、適切な非レンダリング値を使用してください。詳細については、「子コンテンツの削除」を参照してください。
スロットされた子要素へのアクセス
「スロットされた子要素へのアクセス」へのパーマリンクシャドウルート内のスロットに割り当てられた子要素にアクセスするには、標準の slot.assignedNodes
メソッドまたは slot.assignedElements
メソッドを slotchange
イベントと共に使用できます。
たとえば、特定のスロットに割り当てられた要素にアクセスするゲッターを作成できます。
get _slottedChildren() {
const slot = this.shadowRoot.querySelector('slot');
return slot.assignedElements({flatten: true});
}
slotchange
イベントを使用して、割り当てられたノードが変更されたときにアクションを実行することもできます。次の例では、すべてのスロットされた子要素のテキストコンテンツを抽出します。
handleSlotchange(e) {
const childNodes = e.target.assignedNodes({flatten: true});
// ... do something with childNodes ...
this.allText = childNodes.map((node) => {
return node.textContent ? node.textContent : ''
}).join('');
}
render() {
return html`<slot @slotchange=${this.handleSlotchange}></slot>`;
}
詳細については、MDN のHTMLSlotElementを参照してください。
@queryAssignedElements および @queryAssignedNodes デコレーター
「@queryAssignedElements および @queryAssignedNodes デコレーター」へのパーマリンク@queryAssignedElements
と @queryAssignedNodes
は、クラスプロパティを、コンポーネントのシャドウツリー内の特定のスロットで slot.assignedElements
または slot.assignedNodes
をそれぞれ呼び出した結果を返すゲッターに変換します。これらを使用して、特定のスロットに割り当てられた要素またはノードをクエリします。
どちらも、次のプロパティを持つオプションのオブジェクトを受け入れます。
プロパティ | 説明 |
---|---|
flatten | 子 <slot> 要素をその割り当てられたノードに置き換えることで、割り当てられたノードをフラット化するかどうかを指定するブール値。 |
スロット | クエリ対象のスロットを指定するスロット名です。未定義のままにすると、デフォルトのスロットが選択されます。 |
selector (queryAssignedElements のみ) | 指定した場合、このCSSセレクタに一致する割り当て済み要素のみを返します。 |
どのデコレータを使用するかは、スロットに割り当てられたテキストノードをクエリするのか、要素ノードのみをクエリするのかによって異なります。この決定は、ユースケースに固有です。
デコレーターの使用。デコレーターは提案されている JavaScript の機能であるため、デコレーターを使用するには、Babel や TypeScript などのコンパイラを使用する必要があります。詳細については、「デコレーターの使用」を参照してください。
@queryAssignedElements({slot: 'list', selector: '.item'})
_listItems!: Array<HTMLElement>;
@queryAssignedNodes({slot: 'header', flatten: true})
_headerNodes!: Array<Node>;
上記の例は、次のコードと同等です。
get _listItems() {
const slot = this.shadowRoot.querySelector('slot[name=list]');
return slot.assignedElements().filter((node) => node.matches('.item'));
}
get _headerNodes() {
const slot = this.shadowRoot.querySelector('slot[name=header]');
return slot.assignedNodes({flatten: true});
}
レンダリングルートのカスタマイズ
「レンダリングルートのカスタマイズ」へのパーマリンク各Litコンポーネントには、**レンダリングルート**があります。これは、内部DOMのコンテナとして機能するDOMノードです。
デフォルトでは、LitElementは開いているshadowRoot
を作成し、その内部でレンダリングを行い、次のDOM構造を生成します。
<my-element>
#shadow-root
<p>child 1</p>
<p>child 2</p>
LitElementで使用されるレンダリングルートをカスタマイズするには、2つの方法があります。
shadowRootOptions
の設定。createRenderRoot
メソッドの実装。
shadowRootOptions
の設定
「shadowRootOptionsの設定」へのパーマリンク レンダリングルートをカスタマイズする最も簡単な方法は、shadowRootOptions
静的プロパティを設定することです。createRenderRoot
のデフォルトの実装は、コンポーネントのシャドウルートを作成する際に、shadowRootOptions
をオプション引数としてattachShadow
に渡します。これは、ShadowRootInitディクショナリで許可されているオプション(例:mode
とdelegatesFocus
)をカスタマイズするために設定できます。
class DelegatesFocus extends LitElement {
static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true};
}
詳細は、MDNのElement.attachShadow()を参照してください。
createRenderRoot
の実装
「createRenderRootの実装」へのパーマリンク createRenderRoot
のデフォルトの実装は、開いているシャドウルートを作成し、static styles
クラスフィールドに設定されているスタイルを追加します。スタイルに関する詳細は、スタイルを参照してください。
コンポーネントのレンダリングルートをカスタマイズするには、createRenderRoot
を実装し、テンプレートをレンダリングするノードを返します。
たとえば、テンプレートをメインのDOMツリーに要素の子としてレンダリングするには、createRenderRoot
を実装し、this
を返します。
子要素へのレンダリング。子要素へのレンダリングは、シャドウDOMではなく、一般的に推奨されません。要素はDOMまたはスタイルスコープにアクセスできなくなり、内部DOMに要素を合成できなくなります。