外部添加web components Shadow Dom樣式 - 2023-10-12

原文:JavaScript :Web components之給 Shadow DOM 添加樣式(六)

shadow DOM 可以包含 <style><link rel="stylesheet" href="…"> 標簽。在后一種情況下,樣式表是 HTTP 緩存的,因此不會為使用同一模板的多個組件重新下載樣式表。

一般來說,局部樣式只在 shadow 樹內(nèi)起作用,文檔樣式在 shadow 樹外起作用。但也有少數(shù)例外。

:host

:host 選擇器允許選擇 shadow 宿主(包含 shadow 樹的元素)。

例如,我們正在創(chuàng)建 <custom-dialog> 元素,并且想使它居中。為此,我們需要對 <custom-dialog> 元素本身設置樣式。

這正是 :host 所能做的:

<template id="tmpl">
  <style>
    /* 這些樣式將從內(nèi)部應用到 custom-dialog 元素上 */
    :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>
image.png

級聯(lián)

shadow 宿主( <custom-dialog> 本身)駐留在 light DOM 中,因此它受到文檔 CSS 規(guī)則的影響。

如果在局部的 :host 和文檔中都給一個屬性設置樣式,那么文檔樣式優(yōu)先。

例如,如果在文檔中我們有如下樣式:

<style>
custom-dialog {
  padding: 0;
}
</style>

……那么 <custom-dialog> 將沒有 padding。

這是非常有利的,因為我們可以在其 :host 規(guī)則中設置 “默認” 組件樣式,然后在文檔中輕松地覆蓋它們。

唯一的例外是當局部屬性被標記 !important 時,對于這樣的屬性,局部樣式優(yōu)先。

:host(selector)

:host 相同,但僅在 shadow 宿主與 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>
image.png

現(xiàn)在附加的居中樣式只應用于第一個對話框:<custom-dialog centered>

:host-context(selector)

:host 相同,但僅當外部文檔中的 shadow 宿主或它的任何祖先節(jié)點與 selector 匹配時才應用樣式。

例如,:host-context(.dark-theme) 只有在 <custom-dialog> 或者 <custom-dialog> 的任何祖先節(jié)點上有 dark-theme 類時才匹配:

<body class="dark-theme">
  <!-- :host-context(.dark-theme) 只應用于 .dark-theme 內(nèi)部的 custom-dialog -->
  <custom-dialog>...</custom-dialog>
</body>

總之,我們可以使用 :host-family 系列的選擇器來對組件的主元素進行樣式設置,具體取決于上下文。這些樣式(除 !important 外)可以被文檔樣式覆蓋。

給占槽( slotted )內(nèi)容添加樣式

現(xiàn)在讓我們考慮有插槽的情況。

占槽元素來自 light DOM,所以它們使用文檔樣式。局部樣式不會影響占槽內(nèi)容。

在下面的例子中,按照文檔樣式,占槽的 <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>
image.png

結果是粗體,但不是紅色。

如果我們想要在我們的組件中設置占槽元素的樣式,有兩種選擇。

首先,我們可以對 <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>
image.png

這里 <p>John Smith</p> 變成粗體,因為 CSS 繼承在 <slot> 和它的內(nèi)容之間有效。但是在 CSS 中,并不是所有的屬性都是繼承的。

另一個選項是使用 ::slotted(selector) 偽類。它根據(jù)兩個條件來匹配元素:

  1. 這是一個占槽元素,來自于 light DOM。插槽名并不重要,任何占槽元素都可以,但只能是元素本身,而不是它的子元素 。
  2. 該元素與 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>
image.png

請注意,::slotted 選擇器不能用于任何插槽中更深層的內(nèi)容。下面這些選擇器是無效的:

::slotted(div span) { /* 我們插入的 <div> 不會匹配這個選擇器 */ }

::slotted(div) p { /* 不能進入 light DOM 中選擇元素 */ }

此外,::sloated 只能在 CSS 中使用,不能在 querySelector 中使用。

用自定義 CSS 屬性作為勾子

如何在主文檔中設置組件的內(nèi)建元素的樣式?

:host 這樣的選擇器應用規(guī)則到 <custom-dialog> 元素或 <user-card>,但是如何設置在它們內(nèi)部的 shadow DOM 元素的樣式呢?

沒有選擇器可以從文檔中直接影響 shadow DOM 樣式。但是,正如我們暴露用來與組件交互的方法那樣,我們也可以暴露 CSS 變量(自定義 CSS 屬性)來對其進行樣式設置。

自定義 CSS 屬性存在于所有層次,包括 light DOM 和 shadow DOM。

例如,在 shadow DOM 中,我們可以使用 --user-card-field-color CSS 變量來設置字段的樣式,而外部文檔可以設置它的值:

<style>
 .field {
    color: var(--user-card-field-color, black); /* 如果 --user-card-field-color 沒有被聲明過,則取值為 black */ 
  } 
</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,它們在任何地方都可見,因此內(nèi)部的 .field 規(guī)則將使用它。

以下是完整的示例:

<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>
image.png

小結

shadow DOM 可以引入樣式,如 <style><link rel="stylesheet">

局部樣式可以影響:

  • shadow 樹,
  • shadow 宿主(通過 :host-family 系列偽類),
  • 占槽元素(來自 light DOM),::slotted(selector) 允許選擇占槽元素本身,但不能選擇它們的子元素。

文檔樣式可以影響:

  • shadow 宿主(因為它位于外部文檔中)
  • 占槽元素及占槽元素的內(nèi)容(因為它們同樣位于外部文檔中)

當 CSS 屬性沖突時,通常文檔樣式具有優(yōu)先級,除非屬性被標記為 !important,那么局部樣式優(yōu)先。

CSS 自定義屬性穿透 shadow DOM。它們被用作 “勾子” 來設計組件的樣式:

  1. 組件使用自定義 CSS 屬性對關鍵元素進行樣式設置,比如 var(--component-name-title, <default value>)
  2. 組件作者為開發(fā)人員發(fā)布這些屬性,它們和其他公共的組件方法一樣重要。
  3. 當開發(fā)人員想要對一個標題進行樣式設計時,他們會為 shadow 宿主或宿主上層的元素賦值 --component-name-title CSS 屬性。
  4. 奧力給!
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內(nèi)容