Javascript 給 Shadow DOM 添加樣式

2023-02-17 10:59 更新

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

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

:host

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

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

這正是 :host 所能做的:

<template id="tmpl">
  <style>
    /* 這些樣式將從內(nèi)部應(yīng)用到 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>

級(jí)聯(lián)

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

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

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

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

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

這是非常有利的,因?yàn)槲覀兛梢栽谄?nbsp;:host 規(guī)則中設(shè)置 “默認(rèn)” 組件樣式,然后在文檔中輕松地覆蓋它們。

唯一的例外是當(dāng)局部屬性被標(biāo)記 !important 時(shí),對(duì)于這樣的屬性,局部樣式優(yōu)先。

:host(selector)

與 :host 相同,但僅在 shadow 宿主與 selector 匹配時(shí)才應(yīng)用樣式。

例如,我們希望僅當(dāng) <custom-dialog> 具有 centered 屬性時(shí)才將其居中:

<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>

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

:host-context(selector)

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

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

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

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

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

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

占槽元素來自 light DOM,所以它們使用文檔樣式。局部樣式不會(huì)影響占槽內(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>

結(jié)果是粗體,但不是紅色。

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

首先,我們可以對(duì) <slot> 本身進(jìn)行樣式化,并借助 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> 變成粗體,因?yàn)?CSS 繼承在 <slot> 和它的內(nèi)容之間有效。但是在 CSS 中,并不是所有的屬性都是繼承的。

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

  1. 這是一個(gè)占槽元素,來自于 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>

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

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

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

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

用自定義 CSS 屬性作為勾子

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

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

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

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

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

<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>
</style>

然后,我們可以在外部文檔中為 <user-card> 聲明此屬性:

user-card {
  --user-card-field-color: green;
}

自定義 CSS 屬性穿透 shadow DOM,它們?cè)谌魏蔚胤蕉伎梢?,因此?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>

小結(jié)

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

局部樣式可以影響:

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

文檔樣式可以影響:

  • shadow 宿主(因?yàn)樗挥谕獠课臋n中)
  • 占槽元素及占槽元素的內(nèi)容(因?yàn)樗鼈兺瑯游挥谕獠课臋n中)

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

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

  1. 組件使用自定義 CSS 屬性對(duì)關(guān)鍵元素進(jìn)行樣式設(shè)置,比如 ?var(--component-name-title, <default value>) ?。
  2. 組件作者為開發(fā)人員發(fā)布這些屬性,它們和其他公共的組件方法一樣重要。
  3. 當(dāng)開發(fā)人員想要對(duì)一個(gè)標(biāo)題進(jìn)行樣式設(shè)計(jì)時(shí),他們會(huì)為 shadow 宿主或宿主上層的元素賦值 ?--component-name-title? CSS 屬性。
  4. 奧力給!


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)