Shadow DOM 可以包含 <style>
和 <link rel="stylesheet" href="…">
标签。在后一种情况下,样式表是 HTTP 缓存的,因此不会为使用相同模板的多个组件重新下载。
一般来说,本地样式只在 shadow tree 内起作用,而文档样式在 shadow tree 外起作用。但有一些例外。
:host
:host
选择器允许选择 shadow host(包含 shadow tree 的元素)。
例如,我们正在制作一个应该居中的<custom-dialog>
元素。为此,我们需要对<custom-dialog>
元素本身进行样式设置。
这正是:host
的作用。
<
template
id
=
"
tmpl"
>
<
style
>
/* the style will be applied from inside to the custom-dialog element */
:host
{
position
:
fixed;
left
:
50
%
;
top
:
50
%
;
transform
:
translate
(
-50
%
,
-50
%
)
;
display
:
inline-block;
border
:
1
px
solid red
;
padding
:
10
px
;
}
</
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
>
级联
影子宿主(<custom-dialog>
本身)位于光 DOM 中,因此会受到文档 CSS 规则的影响。
如果某个属性在:host
中本地设置了样式,并在文档中也设置了样式,那么文档样式将优先。
例如,如果在文档中我们有
<
style
>
custom-dialog
{
padding
:
0
;
}
</
style
>
…那么<custom-dialog>
将没有填充。
这非常方便,因为我们可以在其:host
规则中设置“默认”组件样式,然后轻松地在文档中覆盖它们。
例外情况是,当本地属性被标记为!important
时,对于此类属性,本地样式将优先。
:host(selector)
与:host
相同,但仅在影子宿主匹配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
:
1
px
solid red
;
padding
:
10
px
;
}
</
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
>
现在,额外的居中样式仅应用于第一个对话框:<custom-dialog centered>
。
总之,我们可以使用:host
系列选择器来设置组件主元素的样式。这些样式(除非!important
)可以被文档覆盖。
设置插槽内容的样式
现在让我们考虑插槽的情况。
插槽元素来自光 DOM,因此它们使用文档样式。本地样式不会影响插槽内容。
在下面的示例中,插槽<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
>
结果是粗体,但不是红色。
如果我们想在组件中设置插槽元素的样式,有两种选择。
首先,我们可以设置<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
>
这里<p>John Smith</p>
变为粗体,因为 CSS 继承在<slot>
及其内容之间生效。但在 CSS 本身中,并非所有属性都继承。
另一个选择是使用::slotted(selector)
伪类。它根据两个条件匹配元素
- 这是一个来自光 DOM 的插槽元素。插槽名称无关紧要。只是任何插槽元素,但只有元素本身,而不是其子元素。
- 该元素与
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
>
请注意,::slotted
选择器无法进一步深入到插槽中。以下选择器无效。
::slotted
(
div span)
{
/* our slotted <div> does not match this */
}
::slotted
(
div)
p
{
/* can't go inside light DOM */
}
此外,::slotted
只能在 CSS 中使用。我们无法在querySelector
中使用它。
使用自定义属性的 CSS 钩子
我们如何从主文档中为组件的内部元素设置样式?
像:host
这样的选择器会将规则应用于<custom-dialog>
元素或<user-card>
,但如何为它们内部的 Shadow DOM 元素设置样式呢?
没有选择器可以从文档中直接影响 Shadow DOM 样式。但就像我们公开方法来与我们的组件交互一样,我们可以公开 CSS 变量(自定义 CSS 属性)来设置其样式。
自定义 CSS 属性存在于所有级别,包括光 DOM 和 Shadow DOM。
例如,在 Shadow DOM 中,我们可以使用--user-card-field-color
CSS 变量来设置字段的样式,而外部文档可以设置其值。
<
style
>
.field
{
color
:
var
(
--user-card-field-color
,
black
)
;
/* if --user-card-field-color is not defined, use black color */
}
</
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,它们在任何地方都可见,因此内部的.field
规则将使用它。
以下是完整的示例。
<
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
>
总结
Shadow DOM 可以包含样式,例如<style>
或<link rel="stylesheet">
。
本地样式可以影响
- Shadow DOM 树,
- 使用
:host
和:host()
伪类的 Shadow 宿主, - 插槽元素(来自光 DOM),
::slotted(selector)
允许选择插槽元素本身,但不包括其子元素。
文档样式可以影响
- Shadow 宿主(因为它位于外部文档中)
- 插槽元素及其内容(因为它们也位于外部文档中)
当 CSS 属性冲突时,通常文档样式具有优先级,除非该属性被标记为!important
。然后本地样式具有优先级。
CSS 自定义属性可以穿透 Shadow DOM。它们被用作“钩子”来设置组件的样式。
- 组件使用自定义 CSS 属性来设置关键元素的样式,例如
var(--component-name-title, <default value>)
。 - 组件作者为开发人员发布这些属性,它们与其他公共组件方法一样重要。
- 当开发者想要为标题设置样式时,他们会为影子宿主或其上层元素分配
--component-name-title
CSS 属性。 - 盈利!