使用 CSS Shadow Parts 样式化 Shadow DOM

Avatar of Ollie Williams
Ollie Williams

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

Safari 13.1 刚刚发布了对 CSS Shadow Parts 的支持。这意味着 ::part() 选择器现在已在 Chrome、Edge、Opera、Safari 和 Firefox 中得到支持。我们将了解它为什么有用,但首先回顾一下 Shadow DOM 的封装……

Shadow DOM 封装的好处

我在 giffgaff 工作,我们拥有种类繁多的 CSS 代码,这些代码多年来由许多不同的人以许多不同的方式编写。让我们考虑一下这可能如何成为问题。

命名冲突

类之间的命名冲突很容易出现在 CSS 中。一个开发者可能会创建一个像 .price 这样的类名。另一个开发者(甚至同一个开发者)可能会在不知情的情况下使用相同的类名。

CSS 不会在此处向您发出任何错误警报。现在,任何具有此类的 HTML 元素都将接收针对两种完全不同事物的样式。

Shadow DOM 解决了这个问题。CSS-in-JS 库(如 Emotionstyled-components)也通过生成随机类名(如 .bwzfXH)以不同的方式解决了此问题。这当然有助于避免冲突!但是,CSS-in-JS 无法阻止任何人以其他方式破坏您的组件。例如……

基本样式和 CSS 重置

可以使用 HTML 元素选择器(如 <button><div>)应用样式。这些样式可能会破坏组件。Shadow DOM 是唯一提供(几乎)完全封装的东西——您可以放心,即使在杂乱的 !important 代码库中,您的组件外观也会保持一致,因为每个组件都已封装。

/* This will have no effect on buttons inside shadow DOM */
button { background-color: lime !important; }

我不会说以这种方式为元素设置样式是一种好的做法,但它确实发生了。即使它确实发生了,这些样式也不会对 Shadow DOM 产生任何影响。

值得注意的是,像 colorfontline-height 这样的可继承样式仍然在 Shadow DOM 中继承。要防止这种情况,请使用 all: initial 或,最好是,在浏览器支持更好后使用 all: revert

让我们来看一个直接应用于 HTML 元素的 CSS 的常见示例。考虑来自 Eric Meyer 的重置 的这段代码:

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
}

如果我们正在处理的组件使用了用户代理的默认边距和填充值会怎样?此重置可能会导致它出现损坏,因为这些默认值实际上被清除了。

Shadow DOM 是一种避免这些问题的方法。Shadow DOM 使我们能够完全确信组件将按预期呈现,无论它最终位于哪个代码库中。同样,没有任何仅用于组件的代码会无意中影响其他任何内容——所有这些都无需诉诸繁重的类命名约定。Shadow DOM 提供了其他任何方法都无法实现的封装级别。

封装很棒,但我们也希望我们的组件可主题化和可自定义。使用 ::part 选择器可以使这变得容易得多。

使用 ::part() 样式化 Shadow DOM

到目前为止,CSS 从 Shadow DOM 外部修改自定义元素样式的唯一方法是使用 CSS 自定义属性。在您只想允许有限更改的严格设计系统中,这可能是理想的。如果您希望您的组件更通用,就会产生问题。您希望提供用于样式设置的每个 CSS 属性都需要使用自定义属性定义。仅仅听到这一点似乎就很乏味。

如果我们想要根据伪类(如 :hover)以不同的方式设置组件的样式,情况会变得更加复杂。基本上,我们最终会得到大量的自定义属性。让我们来看一个来自 Ionic(一个开源 Web 组件集)的示例。看看 Ionic 按钮组件 上定义的所有自定义属性。

来吧,我等着。

我数了 23 个自定义属性。不用说,这不太理想。

这是一个使用 ::part() 设置元素样式的示例。

在这个示例中,我只是更改了 colorborderbackground-color 属性,但我可以使用任何我想要的属性,而不受自定义属性定义的限制。请注意,我还可以使用伪类(如 :hover:focus)为部件的不同状态设置样式。

此按钮示例中的整个组件都公开了样式设置,但如果您的 Web 组件由多个 HTML 元素组成,则您可以仅将组件的选定部分公开给这种样式设置——因此得名 ::part。这阻止了组件的用户对 Shadow 树内的任何任意元素进行样式设置。组件作者有责任公开他们明确想要公开的组件部分。组件的其他部分可以保持视觉上统一或使用自定义属性以实现更少的可自定义性。

那么,我们如何为自己的组件设置这个呢?让我们看看如何使用 ::part 使 Web 组件的某些元素符合样式设置的条件。我们要做的就是在想要公开的元素上添加一个 part 属性。

<div part="box">...</div>  
<button>Click me</button>

在此示例中,div 可以使用 CSS 的全部功能进行自定义——任何 CSS 属性都可以更改。但是,按钮被锁定——除了组件作者之外,任何人都无法对其进行视觉更改。

并且,与 HTML 元素可以有多个类一样,元素也可以有多个 part 名称:

<div part="box thing">...</div>

这就是我们使用 ::part 获得的结果:通过公开元素的“部分”,我们可以提供一些关于如何使用 Web 组件的灵活性,同时在其他区域行使保护。无论它是您的设计系统、组件库还是其他任何内容,事实上 CSS Shadow Parts 正在成为主流,这为我们提供了另一个令人兴奋的工具。