HTML exportparts 全局属性

Baseline 已广泛支持

此特性已经十分成熟,可在许多设备和浏览器版本上使用。自 2020 年 7 月以来,它已在各大浏览器中可用。

exportparts 全局属性允许您通过导出其 part 名称来选择和样式化嵌套 Shadow Tree 中存在的元素。

Shadow Tree 是一个隔离的结构,其中标识符、类和样式无法通过属于常规 DOM 的选择器或查询访问。有两个 HTML 属性可以应用于 Shadow Tree 元素,它们可以从外部定位 Shadow Tree 的 CSS 样式:partexportparts

全局 part 属性使 Shadow Tree 元素对其父 DOM 可见。part 名称用作 ::part() 伪元素的参数。通过这种方式,您可以从 Shadow Tree 外部将其 CSS 样式应用于 Shadow Tree 中的元素。但是,::part() 伪元素仅对父 DOM 可见。这意味着,当 Shadow Tree 嵌套时,这些部分对于直接父级以外的任何祖先都是不可见的。exportparts 属性解决了此限制。

exportparts 属性使 Shadow Tree 的部分在 Shadow DOM 外部可见。这个概念被称为“导出”。exportparts 属性放置在元素的shadow host上,即 Shadow Tree 所附加的元素。此属性的值是 Shadow Tree 中存在的 part 名称的逗号分隔列表。这些名称可供当前结构外部的 DOM 使用。

html
<template id="ancestor-component">
  <nested-component exportparts="part1, part2, part5"></nested-component>
</template>

导出 part 时,您可以选择为该部分分配不同的名称,如下面的代码片段所示。exportparts 属性的值实际上是 part 名称映射的逗号分隔列表。因此,上面的代码片段中的 exportparts 属性等同于 exportparts="part1:part1, part2:part2, part5:part5,表示每个 part 都以相同的名称导出。在每个映射中,第一个字符串指定 Shadow Tree 中部分的名称,第二个字符串指定该部分将在外部暴露的名称。

html
<template id="ancestor-component">
  <nested-component
    exportparts="part1:exposed1, part2:exposed2"></nested-component>
</template>

示例

基本组件

为了演示 exportparts 如何用于启用对嵌套组件内部分的样式设置,我们将创建一个组件,然后将其嵌套在另一个组件中。

HTML

首先,让我们创建一个我们将用另一个组件包装的卡片组件。我们还使用了我们创建的新元素,并用纯文本填充插槽作为内容。

html
<template id="card-component-template">
  <style>
    :host {
      display: block;
    }
  </style>
  <div class="base" part="base">
    <div part="header"><slot name="header_slot"></slot></div>
    <div part="body"><slot name="body_slot"></slot></div>
    <div part="footer"><slot name="footer_slot"></slot></div>
  </div>
</template>

<card-component>
  <p slot="header_slot">This is the header</p>
  <p slot="body_slot">This is the body</p>
  <p slot="footer_slot">This is the footer</p>
</card-component>

JavaScript

我们使用 JavaScript 来定义上面 HTML 中定义的 Web 组件

js
customElements.define(
  "card-component",
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const cardComponent = document.getElementById(
        "card-component-template",
      ).content;
      const shadowRoot = this.attachShadow({
        mode: "open",
      });
      shadowRoot.appendChild(cardComponent.cloneNode(true));
    }
  },
);

CSS

我们使用 ::part 伪元素来样式化 <card-component> Shadow Tree 的部分

css
::part(body) {
  color: red;
  font-style: italic;
}

结果

嵌套组件

在上面的 <card-component> 示例的基础上,我们通过将 <card-component> 包装在另一个组件中来创建一个嵌套组件;在这种情况下,是 <card-wrapper> 组件。然后,我们导出嵌套组件中那些我们希望从组件 Shadow Tree 外部进行样式设置的部分,使用 exportparts 属性。

HTML

html
<template id="card-wrapper">
  <style>
    :host {
      display: block;
    }
  </style>
  <card-component exportparts="base, header, body">
    <slot name="H" slot="header_slot"></slot>
    <slot name="B" slot="body_slot"></slot>
    <slot name="F" slot="footer_slot"></slot>
  </card-component>
</template>

我们包含一个 <card-wrapper> 自定义元素,以及一个 <card-component> 用于对比

html
<h2>Card wrapper</h2>

<card-wrapper>
  <p slot="H">This is the header</p>
  <p slot="B">This is the body</p>
  <p slot="F">This is the footer</p>
</card-wrapper>

<h2>Card component</h2>

<card-component>
  <p slot="header_slot">This is the header</p>
  <p slot="body_slot">This is the body</p>
  <p slot="footer_slot">This is the footer</p>
</card-component>

JavaScript

js
customElements.define(
  "card-wrapper",
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const cardWrapper = document.getElementById("card-wrapper").content;
      const shadowRoot = this.attachShadow({
        mode: "open",
      });
      shadowRoot.appendChild(cardWrapper.cloneNode(true));
    }
  },
);

CSS

现在,我们可以直接定位 <card-component> 的部分,以及当它嵌套在 <card-wrapper> 中时,如下所示

css
h2 {
  background-color: #dedede;
}

card-wrapper,
card-component {
  border: 1px dashed blue;
  width: fit-content;
}

::part(body) {
  color: red;
  font-style: italic;
}

::part(header),
::part(footer) {
  font-weight: bold;
}

结果

注意:当嵌套时,footer 不是粗体,因为我们没有将其包含在 exportparts 中。

暴露映射的部分

为了重命名导出的部分,我们包含一个逗号分隔的映射部分列表,每个映射部分包含原始名称和导出名称,用冒号 (:) 分隔

HTML

我们使用重映射语法更新之前的 <card-wrapper> 自定义元素(从导出的部分列表中省略 body

html
<template id="card-wrapper">
  <card-component
    exportparts="
       base:card__base,
       header:card__header,
       footer:card__footer
     ">
    <span slot="header_slot"><slot name="H"></slot></span>
    <span slot="body_slot"><slot name="B"></slot></span>
    <span slot="footer_slot"><slot name="F"></slot></span>
  </card-component>
</template>

JavaScript

js
customElements.define(
  "card-wrapper",
  class extends HTMLElement {
    constructor() {
      super(); // Always call super first in constructor
      const cardWrapper = document.getElementById("card-wrapper").content;
      const shadowRoot = this.attachShadow({
        mode: "open",
      });
      shadowRoot.appendChild(cardWrapper.cloneNode(true));
    }
  },
);

CSS

在从 <card-wrapper> 中定位 <card-component> 的部分时,我们只能通过其暴露的部分名称来样式化导出的部分

css
/* selects the exported parts name */
::part(card__header) {
  font-weight: bold;
}
/* selects nothing: these part names were not exported */
::part(footer),
::part(body) {
  font-weight: bold;
}

结果

规范

规范
CSS 影子部分
# element-attrdef-html-global-exportparts

浏览器兼容性

另见