CSS 和樣式
本文將探討 Shadow DOM 的眾多強大功能。此類別以 Shadow DOM 101 中討論的概念為基礎。如需相關說明,請參閱該文章。
簡介
面對現實,沒有樣式的標記是沒有可愛的。對我們來說,網頁元件幕後傑出的傑出成員深知這件事,讓我們感到很開心。CSS 範圍模組定義了可在陰影樹狀結構中為內容設定樣式的多個選項。
樣式封裝
影子 DOM 核心功能之一就是「陰影邊界」。這個屬性有許多不錯的屬性 但最棒的是其中一個好處,就是免費提供樣式封裝換個方式說明:
<div><h3>Light DOM</h3></div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
`;
</script>
關於這個示範,有兩項有趣的觀察:
- 本頁還有其他 h3,但只有 h3 選取器符合 h3 選取器的 h3s,因此只有 ShadowRoot 中樣式的紅色。再次強調,預設範圍是限定範圍樣式。
- 在此網頁上定義其他指定 h3 的樣式規則不適用於我的內容。 因為選取器未跨越陰影界線。
故事是早起的嗎?我們融合了外在世界的風格設計,感謝 Shadow DOM!
設定主要元素樣式
:host
可讓您選取代管陰影樹狀結構的元素並設定樣式:
<button class="red">My Button</button>
<script>
var button = document.querySelector('button');
var root = button.createShadowRoot();
root.innerHTML = `
<style>
:host {
text-transform: uppercase;
}
</style>
<content></content>
`;
</script>
其中一項幸運的是,上層頁面中的規則比元素中定義的 :host
規則更明確,但精確度會低於主機元素中定義的 style
屬性。這麼做可讓使用者從外部覆寫您的樣式。:host
也只會在 ShadowRoot 環境中運作,因此無法在 Shadow DOM 外使用。
:host(<selector>)
的功能形式可讓您指定主要元素,使其與 <selector>
相符。
範例 - 只有在元素本身俱有 .different
類別時才進行比對 (例如 <x-foo class="different"></x-foo>
):
:host(.different) {
...
}
回應使用者狀態
:host
的常見用途是,您在建立「自訂元素」,並想要回應不同的使用者狀態 (:懸停、:focus、:active 等)。
<style>
:host {
opacity: 0.4;
transition: opacity 420ms ease-in-out;
}
:host(:hover) {
opacity: 1;
}
:host(:active) {
position: relative;
top: 3px;
left: 3px;
}
</style>
設定元素主題
如果 :host-context(<selector>)
虛擬類別或其任一祖系與 <selector>
相符,就會與主機元素相符。
:host-context()
的常見用途是根據元素的環境設定元素的主題設定。例如,許多人藉由將類別套用至 <html>
或 <body>
來進行主題設定:
<body class="different">
<x-foo></x-foo>
</body>
如果 <x-foo>
是元素的子系,且類別是 .different
類別,您可以 :host-context(.different)
設定 <x-foo>
的樣式:
:host-context(.different) {
color: red;
}
這可讓您在元素的 Shadow DOM 中加入樣式規則,依據其背景設定獨特的樣式。
在單一影子根內支援多種主機類型
:host
的另一個用途是假設您要建立主題設定資料庫,並想要在同一個 Shadow DOM 中為許多類型主機元素設定樣式。
:host(x-foo) {
/* Applies if the host is a <x-foo> element.*/
}
:host(x-foo:host) {
/* Same as above. Applies if the host is a <x-foo> element. */
}
:host(div) {
/* Applies if the host element is a <div>. */
}
從外部設定 Shadow DOM 內部樣式
::shadow
虛擬元素和 /deep/
組合器就像擁有 CSS 授權的 Vorpal 語錄,它們允許穿過陰影 DOM 邊界,為陰影樹狀結構中的元素設定樣式。
::shadow 虛擬元素
如果元素至少有一個陰影樹狀結構,::shadow
虛擬元素會與陰影根本身相符。可讓您編寫選取器,設定節點內部符合元素陰影區的樣式。
舉例來說,如果元素代管陰影根,您可以編寫 #host::shadow span {}
來設定其陰影樹狀結構中的所有跨距。
<style>
#host::shadow span {
color: red;
}
</style>
<div id="host">
<span>Light DOM</span>
</div>
<script>
var host = document.querySelector('div');
var root = host.createShadowRoot();
root.innerHTML = `
<span>Shadow DOM</span>
<content></content>
`;
</script>
範例 (自訂元素):<x-tabs>
的 Shadow DOM 中有 <x-panel>
個子項。每個面板都有自己的陰影樹狀結構,其中包含 h2
標題。如要為主頁中的標題設定樣式,您可以編寫以下程式碼:
x-tabs::shadow x-panel::shadow h2 {
...
}
/deep/ 組合
/deep/
組合與 ::shadow
類似,但功能更強大。它完全會忽略所有陰影邊界,並跨入任意數量的陰影樹。簡單來說,/deep/
可讓您細查元素的引數,並指定任何節點。
/deep/
組合器特別適用於自訂元素世界中特別有用,因為該元件會有多個 Shadow DOM 層級。最主要的範例包括將許多自訂元素 (每個元素都有自己的陰影樹狀結構) 建立巢狀結構,或使用 <shadow>
建立從另一個元素繼承而來的元素。
範例 (自訂元素) - 選取樹狀結構中任何位置的 <x-tabs>
子系 <x-panel>
元素:
x-tabs /deep/ x-panel {
...
}
範例 - 使用 .library-theme
類別來設定所有元素的樣式,位於陰影樹狀結構中的任一處:
body /deep/ .library-theme {
...
}
使用 querySelector()
就像 .shadowRoot
會開啟 DOM 遍歷的影子樹狀結構,組合器會開啟選取器週遊的陰影樹狀結構。您不必編寫巢狀結構的巢狀鏈結,您可以編寫單一陳述式:
// No fun.
document.querySelector('x-tabs').shadowRoot
.querySelector('x-panel').shadowRoot
.querySelector('#foo');
// Fun.
document.querySelector('x-tabs::shadow x-panel::shadow #foo');
設定原生元素樣式
原生 HTML 控制項是設計樣式的一大挑戰。許多人只是放棄並擲出自己的記號不過,只要使用 ::shadow
和 /deep/
,就能為網路平台中使用 Shadow DOM 的任何元素設定樣式。<input>
類型和 <video>
是很好的範例:
video /deep/ input[type="range"] {
background: hotpink;
}
建立風格掛鉤
不過自訂功能是件好事,在某些情況下,您可能想要在影子的樣式盾牌中扣出孔,然後為他人建立掛鉤。
使用 ::shadow 和 /deep/
/deep/
背後有許多功能。這項元件可讓元件作者將個別元素指定為可樣式或大量元素。
範例 - 設定具有 .library-theme
類別的所有元素樣式,並忽略所有陰影樹狀結構:
body /deep/ .library-theme {
...
}
使用自訂虛擬元素
WebKit 和 Firefox 都會定義虛擬元素,用來設定原生瀏覽器元素的內部設定樣式。一個很好的例子就是 input[type=range]
。您可以指定 ::-webkit-slider-thumb
來設定滑桿指標 <span style="color:blue">blue</span>
的樣式:
input[type=range].custom::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: blue;
width: 10px;
height: 40px;
}
與瀏覽器為部分內部設定樣式掛鉤類似,Shadow DOM 內容的作者可以將特定元素指定為可由外方設定樣式。方法是使用自訂虛擬元素來進行。
您可以使用 pseudo
屬性將元素指定為自訂虛擬元素。其值或名稱的前面必須加上「x-」。這麼做會在陰影樹狀結構中與該元素建立關聯,並為外人提供能跨越陰影邊界的指定通道。
以下範例說明如何建立自訂滑桿小工具,並允許他人設定滑桿藍色的樣式:
<style>
#host::x-slider-thumb {
background-color: blue;
}
</style>
<div id="host"></div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<div>
<div pseudo="x-slider-thumb"></div>' +
</div>
`;
</script>
使用 CSS 變數
建立主題掛鉤的強大方法是使用 CSS 變數。基本上就是建立「樣式預留位置」,供其他使用者填入。
假設有一個自訂元素作者,在其 Shadow DOM 中標示變數預留位置。一種用於設定內部按鈕的字型樣式,以及為內部按鈕的字型設定另一個樣式:
button {
color: var(--button-text-color, pink); /* default color will be pink */
font-family: var(--button-font);
}
接著,元素的嵌入程式會依照喜好定義這些值。也許可以比對他們網頁的超酷炫 Comic Sans 主題:
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
基於 CSS 變數繼承的方式,所有內容都很複雜,看起來非常美觀!整張圖如下所示:
<style>
#host {
--button-text-color: green;
--button-font: "Comic Sans MS", "Comic Sans", cursive;
}
</style>
<div id="host">Host node</div>
<script>
var root = document.querySelector('#host').createShadowRoot();
root.innerHTML = `
<style>
button {
color: var(--button-text-color, pink);
font-family: var(--button-font);
}
</style>
<content></content>
`;
</script>
重設樣式
可沿用的樣式 (例如字型、顏色和行高) 會繼續影響 Shadow DOM 中的元素。不過,為了獲得最大的彈性,Shadow DOM 提供 resetStyleInheritance
屬性,以控制陰影邊界的情況。因此您在建立新元件時,也可以從此開始。
resetStyleInheritance
false
- 預設。可沿用的 CSS 屬性會繼續沿用。true
- 在邊界將繼承的屬性重設為initial
。
以下示範了變更 resetStyleInheritance
對陰影樹狀圖的影響:
<div>
<h3>Light DOM</h3>
</div>
<script>
var root = document.querySelector('div').createShadowRoot();
root.resetStyleInheritance = <span id="code-resetStyleInheritance">false</span>;
root.innerHTML = `
<style>
h3 {
color: red;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
`;
</script>
<div class="demoarea" style="width:225px;">
<div id="style-ex-inheritance"><h3 class="border">Light DOM</div>
</div>
<div id="inherit-buttons">
<button id="demo-resetStyleInheritance">resetStyleInheritance=false</button>
</div>
<script>
var container = document.querySelector('#style-ex-inheritance');
var root = container.createShadowRoot();
//root.resetStyleInheritance = false;
root.innerHTML = '<style>h3{ color: red; }</style><h3>Shadow DOM<content select="h3"></content>';
document.querySelector('#demo-resetStyleInheritance').addEventListener('click', function(e) {
root.resetStyleInheritance = !root.resetStyleInheritance;
e.target.textContent = 'resetStyleInheritance=' + root.resetStyleInheritance;
document.querySelector('#code-resetStyleInheritance').textContent = root.resetStyleInheritance;
});
</script>
瞭解 .resetStyleInheritance
有點困難,主要是因為它只會影響可沿用的 CSS 屬性。其中說:當您尋找要沿用的屬性時,在網頁與 ShadowRoot 之間的邊界上,不要沿用主機的值,而是根據 CSS 規格使用 initial
值。
如果不確定 CSS 會沿用哪些屬性,請查看這份方便清單,或切換「元素」面板中的「顯示沿用的項目」核取方塊。
設定分散式節點樣式
分散式節點是在「插入點」 (<content>
元素) 算繪的元素,<content>
元素可讓您從 Light DOM 中選取節點,並算繪到 Shadow DOM 的預先定義位置。這些元素在 Shadow DOM 中並不合邏輯,仍然是主要元素的子項。插入點只是轉譯內容。
分散式節點會保留主要文件的樣式。也就是說,即使元素在插入點算繪,主頁面的樣式規則仍會套用至這些元素。同樣地,分散式節點仍保留在光源中,因此不會移動。而是顯示在其他位置。不過,當節點分散到 Shadow DOM 時,就能採用陰影樹狀結構中定義的其他樣式。
::content 虛擬元素
分散式節點是主機元素的子項,所以我們要如何在 Shadow DOM 的「內」指定這些節點?答案是 CSS ::content
虛擬元素。可以指定通過插入點的 Light DOM 節點。例如:
::content > h3
會為傳遞至插入點的任何 h3
標記設定樣式。
範例如下:
<div>
<h3>Light DOM</h3>
<section>
<div>I'm not underlined</div>
<p>I'm underlined in Shadow DOM!</p>
</section>
</div>
<script>
var div = document.querySelector('div');
var root = div.createShadowRoot();
root.innerHTML = `
<style>
h3 { color: red; }
content[select="h3"]::content > h3 {
color: green;
}
::content section p {
text-decoration: underline;
}
</style>
<h3>Shadow DOM</h3>
<content select="h3"></content>
<content select="section"></content>
`;
</script>
正在重設插入點的樣式
建立 ShadowRoot 時,您可以選擇重設沿用的樣式。<content>
和 <shadow>
插入點也有這個選項。使用這些元素時,請在 JS 中設定 .resetStyleInheritance
,或是在元素本身上使用布林值 reset-style-inheritance
屬性。
若是 ShadowRoot 或
<shadow>
插入點:reset-style-inheritance
代表繼承的 CSS 屬性在主機上設為initial
前,才會觸發陰影內容。這個位置又稱為上邊界。針對
<content>
插入點:reset-style-inheritance
表示可繼承的 CSS 屬性設為initial
,然後再將主機的子項發布至插入點。這個位置稱為「下限」,
結論
身為自訂元素的作者,我們有許多選項可以控制內容的外觀和風格。陰影 DOM 是賦予這個嶄新世界的基礎。
Shadow DOM 提供範圍限定樣式的封裝方式,也可讓你隨心所欲挑選出外界。作者可以定義自訂虛擬元素或包含 CSS 變數預留位置,提供第三方便利的樣式掛鉤,進一步自訂內容。總而言之,網頁作者可以完全掌控內容的呈現方式。