作者定义的 CSS 名称和 shadow DOM:规范和实践

编写定义的 CSS 名称和 shadow DOM 应该协同工作。 然而,各浏览器与规范不一致,有时 其他 CSS 名称的不一致,只是方式略有不同。

本文介绍了由作者定义的 CSS 名称的当前状态 并希望它能指导你改进 在不久的将来实现互操作性。

什么是作者定义的 CSS 名称?

作者定义的 CSS 名称是一种比较旧的 CSS 语法机制,最初 为 @keyframes 规则引入,该规则将 <keyframe-name> 定义为 自定义标识符或字符串此概念的目的是 在样式表的某个部分引用此元素,然后在另一部分引用它。

/* "fade-in" is a CSS name, representing a set of keyframes */
@keyframes fade-in {
  from { opacity: 0 };
  to { opacity: 1 }
}

.card {
  /* "fade-in" is a reference to the above keyframes */
  animation-name: fade-in;
}

其他使用 CSS 名称的 CSS 功能包括字体、属性声明 以及最近的视图转换、锚点定位和 滚动条驱动的动画效果。以下表格虽不全面,但包含名称 Chrome 检查状态时发出的所有请求

功能 名称声明 参考姓名
关键帧 @keyframes animation-name
字体 @font-face { }
@font-palette-values
font-family
font-palette
属性声明 @property 任何自定义属性
查看过渡效果 view-transition-name
view-transition-class
::view-transition-group()
锚点定位 anchor-name position-anchor
滚动条驱动的动画 animation-timeline view-timeline-name
scroll-timeline-name
计数器样式 @counter-style
Counter-reset
counter-set
counter-increment
list-style
容器查询 container-name @container
CSS 变量 --something var(--something)
网页 @page

从下表中可以看出,一个 CSS 名称通常具有对应的 CSS reference。例如,animation-name 是对 @keyframes 的引用 名称。CSS 名称与 DOM 中定义的名称不同,例如属性 进行声明,然后在 样式表。

名称与 shadow DOM 的关系

虽然 CSS 名称是为在内容的不同部分之间 文档或样式表,Shadow DOM 是 相反。它会封装关系,使其不会泄露 它们之间具有自己的命名空间

将 CSS 名称和 shadow DOM 结合在一起,带来 网络组件应该给人以足够的表现力,使其更加灵活,但又受到约束 足够稳定。

从理论上来说,这是很好的做法。实际上,浏览器在呈现 CSS 时 名称可与 shadow DOM 互操作,两者在同一 不同浏览器、各种浏览器以及功能和规范之间的交流。

名称和 shadow DOM 应如何协同工作

要理解这个问题,有必要先了解一下 CSS 的这些部分, 的共同作用。

一般规则

有关 CSS 名称在影子树中行为的一般规则,请参见 CSS 范围第 1 级规范。 总结一下:CSS 名称在其定义的范围内是全局性的,这意味着 可以从后代影子树访问它,但不能从同级或 祖先影子树。请注意,这与网络平台中的名称(如 元素 ID,这些 ID 封装在同一个树形范围内。

该规则的例外情况:@property

与其他 CSS 名称不同,CSS 属性被 shadow DOM 封装。 相反,它们是跨不同阴影传递参数的常见方法 数据。 这样一来, @property 描述符 特殊操作:它的行为应该类似于一个文档全局类型声明 定义了特定命名属性的作用方式。因为房源必须匹配 属性声明不匹配将产生意外的 结果,因此指定要展平和解析 @property 声明 按文档顺序排列

规则应如何与 ::part 协同运作

阴影部分 将影子树内的元素公开给其父树。这样, 父树可以访问该元素,并使用 ::part 设置其样式 元素。

由于 ::part 允许两个树范围为同一元素设置样式,因此以下代码 已指定级联顺序:

  1. 首先,检查 shadow 上下文内的样式。这是“默认” 零件的样式
  2. 然后,应用 ::part 中定义的外部样式。这是 “自定义”零件的样式
  3. 然后,应用与 !important 一起定义的任何内部样式。 这样,自定义元素就可以声明特定属性的特定属性, “::part”不可自定义。

这意味着,shadow DOM 内的名称不能从 ::part,因为 ::part 是主机级范围的样式,而不是阴影范围的样式 样式。例如:

// inside the shadow DOM:
@keyframes fade-in {
  from { opacity: 0}
}

// This shouldn't work!
// The host style shouldn't know the name "fade-in"
::part(slider) {
  animation-name: fade-in;  
}

规则应如何与内嵌样式配合使用

::part 不同的是,内嵌样式使用 style 属性, 使用脚本以编程方式设置样式,作用域限定为元素 作用域。这是因为,要向元素应用样式,您需要 元素句柄,从而传递到影子根本身。

CSS 名称和 shadow DOM 在现实中如何协同工作

虽然上述规则定义明确且一致,但当前 则并非始终会反映这一点。 在实践中,@property 的运作方式与规范一致 并且其他大部分功能都有待解决的错误(其中部分是 尚未发布,因此有时间进行修复)。

为了测试和演示这些功能的实际运用方式,我们制作了 后续页面: https://css-names-in-the-shadow.glitch.me/. 这个网页有几个 iframe,每个 iframe 专注于一项功能,测试六个 场景:

  • 对外部名称的外部引用:不涉及 shadow DOM,应执行此操作 工作。
  • 对内部名称的外部引用:这种方法不可行,因为 表示在影子上下文中定义的名称已泄露。
  • 对外部名称的内部引用:应该可以成功,因为树级范围的名称 由影子根继承。
  • 对内部名称的内部引用:这应该有效,因为 所有引用都在同一个范围内
  • ::part 对外部名称的引用:应该可以成功,因为 ::part 和名称在同一范围内声明。
  • ::part 对内部名称的引用:由于是外部作用域,因此不应起作用。 不应了解 shadow DOM 中声明的名称。

@keyframes

根据规范中的定义,您应该能够引用关键帧名称 从影子根中检索,前提是 @keyframes at-rule 位于祖先实体中 范围。实际上,没有浏览器实施此行为, 只能在定义它们的范围内对其进行引用。请参阅 问题 10540

@property

如规范中所定义,任何 @property 声明都将是 会被展平为文档范围不过,目前,在所有浏览器中 在文档范围内声明 @property,并在文档范围内声明 @property 影子根会被忽略。
请参阅问题 10541

特定于浏览器的错误

其他功能的行为在各浏览器之间不会保持一致:

  • 在 Safari 中,@font-face 已扁平化为根范围。
  • Chromium 不允许继承影子根中的 @anchor-name 规则
  • @scroll-timeline-name@view-timeline-name 的范围不正确 在 ::part 中(同样位于 Chromium 中)。
  • 任何浏览器都不允许在影子根目录中声明 @font-palette-values
  • 可以在影子根中定义 view-transition-class(转场效果) 本身在 shadow-root 之外)。
  • Firefox 允许 ::part 访问内部影子名称(容器查询、 关键帧)。
  • Firefox 和 Safari 不遵循影子根目录中的 @counter-style

请注意,counter-resetcounter-setcounter-increment 因为它们是隐式名称 拥有一套成熟且经过充分测试的规则。

总结

坏消息是,在检查当前互操作状态的快照时, CSS 名称和 shadow DOM 体验是不一致的, 问题。我们在这里探讨的功能 并遵守相关规范 好消息是,要使体验保持一致的增量是有限的, bug 和规范问题列表。让我们解决这个问题! 与此同时,如果您遇到了 不一致性。