Shadow DOM 可讓網頁開發人員為網頁元件建立區隔式 DOM 和 CSS
摘要
Shadow DOM 讓建構網頁應用程式再也不必費心。極地
來自全球的 HTML、CSS 和 JS。多年來,我們
發明瞭一個超長的數字
/
工具
規避問題例如,使用新的 HTML ID/類別時
因此無法判斷這會與網頁使用的名稱發生衝突。
系統偵測到細微錯誤
CSS 的具體性會成為一大問題 (這全都是 !important
、什麼!)、樣式
許多控制項都不在掌控權範圍內
效能可能會受到影響。清單
繼續。
Shadow DOM 修正 CSS 和 DOM。為此,我們針對網路推出範圍樣式。 平台。如果沒有工具或命名慣例,您可以將 CSS 與 標記、隱藏實作詳細資料,作者獨立成 元件。
簡介
Shadow DOM 是 Web 元件標準的三項標準之一: HTML 範本、 Shadow DOM 和 自訂元素。 HTML 匯入項目 原本就包含在名單中 已淘汰。
您不需要編寫使用 shadow DOM 的網頁元件。但當你這麼做 您可以運用這個平台的優點 (CSS 範圍設定、DOM 封裝、 建構可重複使用 自訂元素 彈性、設定高度且可重複使用如為自訂值 元素是建立新 HTML (使用 JS API) 的方式,shadow DOM 是 。這兩個 API 會結合在一起 並搭配獨立的 HTML、CSS 和 JavaScript 使用
Shadow DOM 旨在協助您建構以元件為基礎的應用程式。因此 它帶來網路開發的常見問題解決方案:
- 獨立 DOM:元件的 DOM 為獨立性質 (例如
document.querySelector()
不會傳回元件的 shadow DOM 中的節點)。 - 限定範圍的 CSS:在 shadow DOM 內定義的 CSS 範圍限定為該 CSS。樣式規則 請不要洩漏,而且頁面樣式不會出血。
- 組合:為元件設計宣告式標記式 API。
- 簡化 CSS - 限定範圍的 DOM 可讓您使用簡單的 CSS 選取器, 通用 ID/類別名稱,無須擔心命名衝突。
- 效率提升:請將應用程式視為 DOM 區塊,而非大型語言 (全域) 網頁中。
fancy-tabs
示範
在這篇文章中,我將是指示範元件 (<fancy-tabs>
)
並從中參照程式碼片段如果瀏覽器支援 API
應該就能看到這項功能的現場示範否則,請查看 GitHub 上的完整原始碼。
什麼是 shadow DOM?
DOM 背景
HTML 是網路的驅動力,因為易於使用。只要宣告幾個標記 而且只要幾秒鐘就能編寫具有呈現和結構的網頁不過 因為 HTML 本身並不是最實用的。對於人類來說,這是一件容易理解的文字- 但機器需要更多機制輸入文件物件 Model,也就是 DOM
瀏覽器載入網頁時,會做許多有趣的事。下列其中一項 它會將作者的 HTML 轉換為即時文件 基本上,為了瞭解網頁結構,瀏覽器會剖析 HTML ( 轉換為資料模型 (物件/節點)。瀏覽器會保留 藉由建立這些節點的樹狀結構,也就是 DOM 的階層結構。酷炫內容 DOM 是網頁即時呈現的內容不同於 我們編寫的 HTML,瀏覽器產生的節點包含屬性、方法,以及最佳 都能透過程式操控!這就是我們能夠建立 DOM 的原因 直接使用 JavaScript 元素:
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,有兩個差異:1) 如何建立/使用物件,以及
2) 網頁相對於其他網頁的行為。通常您需要建立 DOM
並附加為另一個元素的子項有了 shadow DOM
建立一個限定範圍的 DOM 樹狀結構,附加至元素,但與元素分開
實際的子女。這種範圍的子樹狀結構稱為「陰影樹狀結構」。元素
附加至用戶端的影子主機。您在陰影中新增的內容
其本機位置,包括 <style>
。這就是 shadow DOM 的
會達到 CSS 樣式範圍設定
正在建立 shadow DOM
陰影根是指附加至「主機」元素的文件片段。
附加陰影根目錄的做法,是元素取得陰影 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
填滿陰影根目錄,但您也可以使用其他 DOM
相互整合也就是網路。我們別無選擇。
這個規格會定義元素清單 無法代管影子樹有幾個原因會導致元素出現 清單:
- 瀏覽器已為元素代管本身的內部 shadow DOM
(
<textarea>
、<input>
)。 - 元素要代管陰影 DOM (
<img>
) 並不合理。
舉例來說,這就無效:
document.createElement('input').attachShadow({mode: 'open'});
// Error. `<input>` cannot host shadow dom.
為自訂元素建立 shadow DOM
陰影 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>
`;
}
...
});
這裡有幾個有趣的事。首先
自訂元素會在 <fancy-tabs>
例項時建立自己的 shadow DOM
也就是在 constructor()
中完成。其次,由於系統將建立
陰影根層級,<style>
內的 CSS 規則的範圍會限定為 <fancy-tabs>
。
組合和運算單元
組合是 Shad DOM 最容易理解的特徵之一 是最重要的
在網頁程式開發的領域中,合成就是建構應用程式的方式
。不同的建構模塊 (<div>
、<header>
、
<form>
、<input>
) 會合而為一。部分廣告代碼甚至可以運作
應用程式設計組合是 <select>
、
<details>
、<form>
和 <video>
非常靈活。每個廣告代碼都接受
就某些 HTML 程式碼來說 很類似例如:
<select>
知道如何將 <option>
和 <optgroup>
算繪為下拉式選單並
提供多選項小工具。<details>
元素會將 <summary>
算繪為
可展開的箭頭。就連 <video>
也知道如何處理特定子項:
<source>
元素不會算繪,但會影響影片行為。
真了不起!
術語:Light DOM 與陰影 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>
陰影 DOM
元件作者編寫的 DOM。Shadow DOM 是元件的本機位置 定義其內部結構、限定範圍的 CSS,並封裝您的實作 詳細資料。也可定義如何轉譯由消費者編寫的標記 元件
#shadow-root
<style>...</style>
<slot name="icon"></slot>
<span id="wrapper">
<slot>Button</slot>
</span>
壓平合併 DOM 樹狀結構
瀏覽器將使用者的 Light DOM 發布到陰影中的結果 DOM 轉譯最終產品一棵平整的樹木 以及頁面上顯示的內容
<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>元素
陰影 DOM 會使用 <slot>
元素組合不同的 DOM 樹狀結構。
版位是元件中的預留位置,使用者「可以」填入版位
標記。定義一或多個版位後,即可邀請外部標記顯示
嵌入至元件的 shadow DOM 中基本上,您是在說「轉譯使用者的
標記在這裡」。
元素可以「交叉」在 <slot>
邀請時加入陰影 DOM 界線
。這些元素稱為「分散式節點」。概念上
分散各地的節點似乎有些奇怪運算單元不會實際移動 DOM;他們
會算繪在 shadow DOM 中的其他位置。
元件可在其 shadow DOM 中定義零個或多個版位。運算單元可留空 或提供備用內容如果使用者未提供 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>
確實如此!
樣式
設定網頁元件樣式的選項有很多種。使用陰影的元件 DOM 可由主頁面設定樣式、定義專屬樣式,或提供掛鉤 ( CSS 自訂屬性的形式),方便使用者覆寫預設值。
元件定義的樣式
將 shadow DOM 最實用的功能向下交給「範圍 CSS」:
- 外部頁面的 CSS 選取器不會套用至元件。
- 內部定義的樣式不會出血。存取範圍僅限主要元素。
shadow DOM 中使用的 CSS 選取器會套用至您的元件。於 這意味著我們可以再次使用通用 ID/類別名稱,而不必擔心 。更簡單的 CSS 選取器是最佳做法 在 Shadow DOM 內部也有助於提升成效。
範例 - 陰影根層級中定義的樣式為本機
#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-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
只會在陰影根層級下運作,因此不能在
shadow DOM。
: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>
元件的陰影 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>
提供的另一個深入範例:
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>
`;
在這個範例中,有兩個版位:分頁標題的已命名版位
分頁面板內容的版位使用者選取分頁時,系統會以粗體顯示其選取項目
然後顯示面板做法是選取含有
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
。
進階主題
建立封閉式陰影根 (應避免)
還有另一種名為「closed」的 shadow DOM 變種版本模式。建立 Deployment 時
封閉的陰影樹狀結構,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.assignedSlot
會傳回null
Event.composedPath()
適用於與陰影內部元素相關聯的事件 傳回 []
以下摘要說明 不應該使用 建立網頁元件的原因
{mode: 'closed'}
:
人造的安全感,沒有什麼原因可以阻止攻擊者 盜用
Element.prototype.attachShadow
。關閉模式會禁止自訂元素程式碼存取其本身的 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'); } ... });
關閉模式會讓使用者較不靈活元件。如同你 建構網頁元件後,萬一您忘了加入 而不是每個特徵的分數設定選項。使用者想要的用途。常見的 就是忘記為內部節點加入適當的樣式掛鉤。 在封閉模式下,使用者無法覆寫預設值及調整設定 。存取元件內部資源至關重要, 使用者最終會使用您的元件建立分支、尋找其他元件或 如果工具未應做什麼,便可以提供建議 :(
在 JS 中使用運算單元
shadow DOM API 可用來處理版位和分散式資源的公用程式 節點。這在編寫自訂元素時可派上用場。
運算單元變更事件
當運算單元的分散式節點變更時,會觸發 slotchange
事件。適用對象
例如,使用者從光 DOM 新增/移除子項。
const slot = this.shadowRoot.querySelector('#slot');
slot.addEventListener('slotchange', e => {
console.log('light dom children changed!');
});
如要監控其他類型的 Light DOM 變更類型,請設定
MutationObserver
敬上
。
哪些元素在版位中算繪?
有時候,瞭解與版位相關聯的元素會很有幫助。致電
slot.assignedNodes()
:找出版位正在算繪的元素。
{flatten: true}
選項也會傳回版位的備用內容 (如果沒有節點的話)
)。
舉例來說,假設陰影 DOM 如下所示:
<slot><b>fallback content</b></slot>
用量 | 撥打電話 | 結果 |
---|---|---|
<my-component>元件文字</my-component> | slot.assignedNodes(); |
[component text] |
<my-component></my-component> | slot.assignedNodes(); |
[] |
<my-component></my-component> | slot.assignedNodes({flatten: true}); |
[<b>fallback content</b>] |
元素會指派給哪個版位?
回答反向問題也可以。element.assignedSlot
分享
您指派元素的哪個元件版位。
Shadow DOM 事件模型
事件從 shadow DOM 消失時,其目標會調整以維持 封裝 shadow DOM 提供的也就是說,事件必須重新指定 而不是來自元件的內部元素 shadow DOM。有些事件甚至不會從 shadow DOM 傳播。
「確實」跨越陰影邊界的事件如下:
- 焦點活動:
blur
、focus
、focusin
、focusout
- 滑鼠事件:
click
、dblclick
、mousedown
、mouseenter
、mousemove
等 - 車輪事件:
wheel
- 輸入事件:
beforeinput
、input
- 鍵盤事件:
keydown
、keyup
- 組成事件:
compositionstart
、compositionupdate
、compositionend
- DragEvent:
dragstart
、drag
、dragend
、drop
等。
使用提示
如果陰影樹狀結構開啟,呼叫 event.composedPath()
會傳回陣列
事件行經的節點數量
使用自訂事件
無法在陰影樹狀結構中於內部節點上觸發的自訂 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 的事件模型,所觸發的事件
陰影 DOM 內部會經過調整,看起來像是來自代管元素
舉例來說,假設您點選了陰影根目錄中的 <input>
:
<x-focus>
#shadow-root
<input type="text" placeholder="Input inside shadow dom">
focus
事件看起來是來自 <x-focus>
,而不是 <input>
。
同樣地,document.activeElement
會是 <x-focus>
。如果影子根
是使用 mode:'open'
建立 (請參閱關閉模式),您也會
存取獲得重點的內部節點:
document.activeElement.shadowRoot.activeElement // only works with open mode.
如果遊戲中有多個陰影 DOM 層級 (例如
也就是另一個自訂元素),您需要以遞迴方式深入查看陰影根層級,以便
找出 activeElement
:
function deepActiveElement() {
let a = document.activeElement;
while (a && a.shadowRoot && a.shadowRoot.activeElement) {
a = a.shadowRoot.activeElement;
}
return a;
}
另一個聚焦選項是 delegatesFocus: true
選項,可展開
陰影樹狀結構中元素的焦點行為:
- 如果您在 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>
結果
上方是聚焦於 <x-focus>
時的結果 (使用者點選、分頁進入、
focus()
等),「可點擊的 Shadow DOM 文字」或內部容器的
<input>
已聚焦 (包括 autofocus
)。
如果設定 delegatesFocus: false
,則會看到以下內容:
提示和秘訣
多年來,我學到一兩件有關編寫網頁元件的知識I 相信這些秘訣對元件編寫和技術的運用很有幫助 對 shadow DOM 偵錯。
使用 CSS 納入
一般來說,網頁元件的版面配置/樣式/繪製作業相當獨立。使用
:host
中適用於 Perf 的 CSS 容器
勝利:
<style>
:host {
display: block;
contain: content; /* Boom. CSS containment FTW. */
}
</style>
重設可繼承的樣式
可沿用樣式 (background
、color
、font
、line-height
等) 繼續
才會在 shadow DOM 中沿用也就是說,它們會將陰影 DOM 邊界旋轉
預設值。如要從新的插入畫面開始設定,請使用 all: initial;
進行重設
當跨越陰影邊界時,可繼承的樣式就會成為其初始值。
<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 為
一些時間。但 Blink 會繼續同時支援兩種版本
讓應用程式從可以最快做出回應的位置
回應使用者要求第 0 版規格提供不同的方法建立影子根目錄
(element.createShadowRoot
,而非 v1 的 element.attachShadow
)。呼叫
舊版方法會繼續建立使用 v0 語意的陰影根目錄
不會中斷
如果您想瞭解舊版 v0 規格,請參閱 html5rocks 文章: 1、 2、 3: 還有非常棒的比較 shadow DOM v0 和 v1 之間的差異。
瀏覽器支援
Shadow DOM v1 於 Chrome 53 版 (狀態) 發布。 Opera 40、Safari 10 和 Firefox 63。邊緣 已開始開發
如要功能偵測陰影 DOM,請檢查 attachShadow
是否存在:
const supportsShadowDOMV1 = !!HTMLElement.prototype.attachShadow;
聚合物
在此之前, shadycss polyfills 可提供第 1 版 而不是每個特徵的分數Shady DOM 模仿 DOM 調整 Shadow DOM 範圍和陰影 polyfill CSS 自訂屬性和樣式設定原生 API 提供的樣式範圍。
安裝 polyfill:
bower install --save webcomponents/shadydom
bower install --save webcomponents/shadycss
使用 polyfill:
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>
等舊式行李的行李。
不要弄錯了。影子 DOM 真的是很複雜的動物!但這是個獸 值得學習花一點時間瞭解它。瞭解並提出問題!
延伸閱讀
- Shadow DOM v1 和 v0 之間的差異
- 「隆重推出 Slot-based Shadow DOM API」 。
- 網頁元件與模組 CSS 的未來 上傳者:Philip Walton
- 「自訂元素:建構可重複使用的網頁元件」 使用 Google WebFundamentals 成品
- Shadow DOM v1 規格
- 自訂元素 v1 規格
常見問題
我今天可以使用 Shadow DOM v1 嗎?
使用 polyfill 就代表肯定的請參閱瀏覽器支援。
shadow DOM 提供哪些安全性功能?
Shadow DOM 並非安全防護功能。這是一種輕量化工具
可用來指定 CSS 範圍
以及隱藏元件中的 DOM 樹狀結構如果您需要真正的資安防護範圍
使用 <iframe>
。
網頁元件需要使用 shadow DOM 嗎?
不對!您不需要建立使用 shadow DOM 的網頁元件。不過 編寫使用 Shadow DOM 的自訂元素後, 善用 CSS 範圍、DOM 封裝和組合等功能。
開放和封閉的影子根之間有何差異?
請參閱「封閉陰影根層級」一文。