Shadow DOM 可能同時包含 <style>
和 <link rel="stylesheet" href="…">
標籤。在後一種情況下,樣式表會快取在 HTTP 中,因此不會為使用相同範本的多個元件重新下載。
一般來說,區域樣式只會在 shadow tree 內運作,而文件樣式則會在 shadow tree 外運作。但有少數例外。
:host
:host
選擇器允許選擇 shadow host(包含 shadow tree 的元素)。
例如,我們正在製作應該置中的 <custom-dialog>
元素。為此,我們需要設定 <custom-dialog>
元素本身的樣式。
這正是 :host
所做的
<template id="tmpl">
<style>
/* the style will be applied from inside to the custom-dialog element */
:host {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: inline-block;
border: 1px solid red;
padding: 10px;
}
</style>
<slot></slot>
</template>
<script>
customElements.define('custom-dialog', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'}).append(tmpl.content.cloneNode(true));
}
});
</script>
<custom-dialog>
Hello!
</custom-dialog>
串接
影子主機 (<custom-dialog>
本身) 位於 light DOM 中,因此會受到文件 CSS 規則的影響。
如果有一個屬性在 :host
本地和文件中都設定了樣式,則文件樣式優先。
例如,如果在文件中我們有
<style>
custom-dialog {
padding: 0;
}
</style>
…則 <custom-dialog>
將沒有內距。
這非常方便,因為我們可以在其 :host
規則中設定元件的「預設」樣式,然後在文件中輕鬆覆寫它們。
例外情況是,當一個本地屬性標記為 !important
時,對於此類屬性,本地樣式優先。
:host(selector)
與 :host
相同,但僅在影子主機與 selector
匹配時套用。
例如,我們希望僅當 <custom-dialog>
具有 centered
屬性時才將其置中
<template id="tmpl">
<style>
:host([centered]) {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
border-color: blue;
}
:host {
display: inline-block;
border: 1px solid red;
padding: 10px;
}
</style>
<slot></slot>
</template>
<script>
customElements.define('custom-dialog', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'}).append(tmpl.content.cloneNode(true));
}
});
</script>
<custom-dialog centered>
Centered!
</custom-dialog>
<custom-dialog>
Not centered.
</custom-dialog>
現在,額外的置中樣式僅套用於第一個對話方塊:<custom-dialog centered>
。
總之,我們可以使用 :host
家族的選取器來設定元件主元素的樣式。這些樣式(除非 !important
)可以被文件覆寫。
設定插槽內容的樣式
現在讓我們考慮插槽的情況。
插槽元素來自 light DOM,因此它們使用文件樣式。本地樣式不會影響插槽內容。
在以下範例中,插槽 <span>
根據文件樣式為粗體,但不會從本地樣式取得 background
<style>
span { font-weight: bold }
</style>
<user-card>
<div slot="username"><span>John Smith</span></div>
</user-card>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<style>
span { background: red; }
</style>
Name: <slot name="username"></slot>
`;
}
});
</script>
結果是粗體,但不是紅色。
如果我們想要在元件中設定插槽元素的樣式,有兩種選擇。
首先,我們可以設定 <slot>
本身的樣式,並依賴 CSS 繼承
<user-card>
<div slot="username"><span>John Smith</span></div>
</user-card>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<style>
slot[name="username"] { font-weight: bold; }
</style>
Name: <slot name="username"></slot>
`;
}
});
</script>
這裡 <p>John Smith</p>
變成粗體,因為 CSS 繼承在 <slot>
及其內容之間有效。但在 CSS 本身中,並非所有屬性都會繼承。
另一個選項是使用 ::slotted(selector)
偽類別。它根據兩個條件來匹配元素
- 這是一個插槽元素,來自 light DOM。插槽名稱無關緊要。只是任何插槽元素,但僅限元素本身,不包括其子元素。
- 該元素與
selector
相符。
在我們的範例中,::slotted(div)
精確選取 <div slot="username">
,但不會選取其子元素
<user-card>
<div slot="username">
<div>John Smith</div>
</div>
</user-card>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<style>
::slotted(div) { border: 1px solid red; }
</style>
Name: <slot name="username"></slot>
`;
}
});
</script>
請注意,::slotted
選擇器無法進一步深入槽。下列選擇器無效
::slotted(div span) {
/* our slotted <div> does not match this */
}
::slotted(div) p {
/* can't go inside light DOM */
}
此外,::slotted
只能用於 CSS。我們無法在 querySelector
中使用它。
使用自訂屬性的 CSS 掛鉤
我們如何從主文件設定元件的內部元素樣式?
例如 :host
的選擇器會將規則套用至 <custom-dialog>
元素或 <user-card>
,但如何設定它們內部的 Shadow DOM 元素樣式?
沒有任何選擇器可以直接從文件影響 Shadow DOM 樣式。但就像我們公開方法以與元件互動一樣,我們可以公開 CSS 變數(自訂 CSS 屬性)來設定其樣式。
自訂 CSS 屬性存在於所有層級,無論是光 DOM 還是 Shadow DOM。
例如,在 Shadow DOM 中,我們可以使用 --user-card-field-color
CSS 變數來設定欄位的樣式,而外部文件可以設定其值
<style>
.field {
color: var(--user-card-field-color, black);
/* if --user-card-field-color is not defined, use black color */
}
</style>
<div class="field">Name: <slot name="username"></slot></div>
<div class="field">Birthday: <slot name="birthday"></slot></div>
然後,我們可以在外部文件為 <user-card>
宣告此屬性
user-card {
--user-card-field-color: green;
}
自訂 CSS 屬性會穿透 Shadow DOM,它們在所有地方都可見,因此內部的 .field
規則會使用它。
以下是完整範例
<style>
user-card {
--user-card-field-color: green;
}
</style>
<template id="tmpl">
<style>
.field {
color: var(--user-card-field-color, black);
}
</style>
<div class="field">Name: <slot name="username"></slot></div>
<div class="field">Birthday: <slot name="birthday"></slot></div>
</template>
<script>
customElements.define('user-card', class extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.append(document.getElementById('tmpl').content.cloneNode(true));
}
});
</script>
<user-card>
<span slot="username">John Smith</span>
<span slot="birthday">01.01.2001</span>
</user-card>
摘要
Shadow DOM 可以包含樣式,例如 <style>
或 <link rel="stylesheet">
。
區域樣式會影響
- Shadow 樹
- 使用
:host
和:host()
偽類別的 Shadow 主機 - 槽元素(來自光 DOM),
::slotted(selector)
允許選擇槽元素本身,但不包含其子元素。
文件樣式會影響
- Shadow 主機(因為它存在於外部文件)
- 槽元素及其內容(因為它們也存在於外部文件)
當 CSS 屬性衝突時,通常文件樣式有優先權,除非屬性標示為 !important
。此時,區域樣式有優先權。
CSS 自訂屬性會穿透 Shadow DOM。它們用作設定元件樣式的「掛鉤」
- 元件使用自訂 CSS 屬性來設定關鍵元素的樣式,例如
var(--component-name-title, <default value>)
。 - 元件作者會為開發人員公開這些屬性,它們與其他公開元件方法一樣重要。
- 當開發人員想要設定標題樣式時,他們會為 Shadow 主機或其上層指定
--component-name-title
CSS 屬性。 - 獲利!
留言
<code>
標籤,對於多行程式碼 - 將其包裝在<pre>
標籤中,對於超過 10 行的程式碼 - 使用沙盒 (plnkr、jsbin、codepen…)