shadow-dom-overlay-insertion について
shadow-dom-overlay-insertionは、Shadow DOM内にスロット配置されるWebコンポーネントへのオーバーレイ挿入に関する技術リファレンスです。挿入先の選び方と、LitのShadow DOM描画前に生じるタイミング差への対処を説明します。
主な機能
- 01`assignedSlot`によるスロット配置の検出
- 02Shadow DOM内部コンテナへの挿入
- 03描画前のスロット割り当てへの対処
- 04DOM状態に応じた挿入方法の選択
活用シーン
- 01スロット配置されたWebコンポーネントへのオーバーレイ追加
- 02オーバーレイがホストのlight DOMに入るのを防止
- 03Litの描画後に挿入を再試行
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ editframe/ skills --skill shadow-dom-overlay-insertionインストール後、Claude Code で/shadow-dom-overlay-insertionを入力して読み込まれたか確認します。
詳細
シャドウ DOM へのオーバーレイ挿入
問題
コンポーネント A(例:EFCanvas)が自身の隣にオーバーレイを DOM に挿入しようとすると、通常は this.parentElement.appendChild(overlay) を使用します。A がシャドウ DOM ホスト(例:EFWorkbench)内にスロットとして配置されている場合、これは正常に動作しません。
EFWorkbench (shadow host)
shadow root
<div class="canvas-slot-container"> ← the real DOM parent
<slot name="canvas"> ← slot element
[EFCanvas is assigned here] ← light DOM, assigned via slot
efCanvas.parentElementは内部コンテナーではなく、シャドウホストを返しますshadowHost.appendChild(overlay)を呼び出すと、オーバーレイはホストの ライト DOM に配置されます。シャドウ DOM の CSS と z-index が適用されないため、オーバーレイが表示されません
修正パターン
private insertOverlay(overlay: HTMLElement): void {
const slot = this.assignedSlot;
if (slot) {
// Correctly slotted: insert into the shadow-internal container
slot.parentElement?.appendChild(overlay);
return;
}
if (this.parentElement?.shadowRoot) {
// Shadow DOM exists but slot assignment hasn't happened yet
// (connectedCallback fires before Lit renders shadow DOM).
// Return early; caller should retry via requestAnimationFrame.
return;
}
// Not slotted — plain DOM, safe to use parentElement
this.parentElement?.appendChild(overlay);
}
タイミングに関する注意点
HTML の解析中、Lit がシャドウ DOM をレンダリングする前に connectedCallback が実行されます。この時点では:
this.assignedSlotはnullです(スロットの割り当てがまだ行われていません)this.parentElementは null ではありません(シャドウホストです)this.parentElement.shadowRootも null です(シャドウ DOM がまだアタッチされていません)
同じマイクロタスクの実行中に後から(Lit の performUpdate の後に):
this.assignedSlotは<slot>要素になりますslot.parentElementは内部コンテナーになります
ガードパターン:
connectedCallback() {
super.connectedCallback();
this.tryInsertOverlay();
}
private tryInsertOverlay(): void {
if (!this.assignedSlot && this.parentElement?.shadowRoot) {
// Too early — shadow host exists but slot not yet assigned.
// Lit will assign the slot after performUpdate.
requestAnimationFrame(() => this.tryInsertOverlay());
return;
}
this.insertOverlay(this.overlay);
}
ルールのまとめ
| 状況 | assignedSlot | parentElement.shadowRoot | 対応 |
|---|---|---|---|
| スロットに配置されていない | null | null/undefined | parentElement.appendChild を使用 |
| 正しくスロットに配置されている | non-null | (任意) | assignedSlot.parentElement.appendChild を使用 |
| 早すぎる場合(Lit が未レンダリング) | null | non-null | 早期に return し、RAF 経由で再試行 |
| スロットに配置され、親にシャドウがない | null | null | parentElement.appendChild を使用 |
親がシャドウ DOM ホストの場合、this.parentElement を直接使用しないでください。
コメント
Markdown 対応