什么是插槽?

Web Components提供了使用JavaScript、CSS和HTML创建组件的巧妙功能。它们自2017年以来已经在所有主要的网络浏览器中可用。这已经足够长时间开始在生产中使用它们了。

Web Components由三个旨在一起工作的组成部分组成。

  1. 自定义元素
  2. Shadow DOM
  3. 模板和插槽

我们详细地研究插槽和<slot>元素。

什么是插槽?

为了使插槽起作用,我们需要一个Shadow DOMMDN关于Shadow DOM的定义如下:

一组JavaScript API,用于将封装的Shadow DOM树附加到一个元素上——该树与主文档DOM分开呈现——并控制相关功能。通过这种方式,您可以将元素的特性保持私有,以便可以对其进行脚本化和样式化,而不用担心与文档的其他部分发生冲突。

插槽只存在于Shadow DOM中,并且是从文档(即网页)获取内容的一种方式。
实际上,我们有两种DOM。一个是轻DOM(文档),另一个是附加到自定义元素或基本HTML元素的Shadow DOM

是的,您可以将一个Shadow DOM关联到一个<div>、一个<section>元素或一个<main>元素上!
插槽相当神奇,可以在这两个DOM之间极其有用地进行桥接。
解释插槽是什么的最佳方法是向您展示。

这里是一个例子

假设您想要在自定义元素和一个<template>中使用一个<slot>。为了简洁起见,我只展示了两个HTML部分,而不是整个自定义元素设置。

自定义元素标记

这是您将在网页上编写的HTML。

1
2
3
4
<card-component>
<h2 slot="heading">A heading passed to the named slot 'heading'.</h2>
<blockquote>This will be passed on to the unnamed slot. Lorem ipsum dolor...</blockquote>
</card-component>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Get the DIV 
const div = document.getElementById("demodiv");

// Get the Template
const { content } = document.getElementById('template');

// Attach a shadowRoot to the DIV
const shadowRoot = div.attachShadow({ mode: "closed" });

// Insert the template into the shadowRoot
shadowRoot.appendChild(content.cloneNode(true));

// Show the DIV
div.removeAttribute('hidden');
您的Shadow DOM的模板

模板被“附加”到自定义元素内部的Shadow DOM中,<slot>拉取内容进来。自定义元素现在使用来自页面的内容渲染模板。

1
2
3
4
5
6
7
8
<template>
<a id="linkheader">
<slot name="heading"></slot>
</a>
<div id="content">
<slot></slot>
</div>
</template>

关键在于您可以将<slot>放入模板提供的更复杂的标记中。

脆皮的部分

以下是使插槽出色的几个要点:

  • Shadow DOM内部的内容并不总是像在普通网页上那样易于访问。插槽允许您的内容同时存在于两者中。内容保留在页面上,而 Shadow DOM可以访问并增强它。
  • 插槽可以按任何顺序放置。特别是对于命名插槽,正如我们在上面的示例中所见。在文档源中,“heading”是第一个元素,但通过模板显示为第二个元素。
    这样做是为了方便机器索引(即搜索引擎和爬虫,如ChatGPT),因为内容仍然在网页上。注意:大多数搜索引擎现在会索引 Shadow DOM的内容。
  • 页面上的内容变化会传递到 Shadow DOM。插槽是实时的,响应式的!
  • 您不需要对插槽元素进行样式设置,因为网页样式已经对它们进行了样式设置。样式与内容一起传递。

这听起来都很不错。但是,在我们被未关闭的<article>给卡住之前,让我们看看插槽有什么让人不爽的地方。

使用遇到的问题

这部分需要更多关于 Shadow DOM行为的解释。换句话说,它的封装性。
CSS-TricksWeb组件有一个介绍,并包含了关于 Shadow DOM的有用部分:

Shadow DOM的工作方式有点像<iframe>,其中内容与文档的其余部分隔离开来;但是,当我们创建一个影子根时,我们仍然可以完全控制页面的那部分,但仅限于一个上下文。

对我来说,这是Web组件的杀手功能。没有东西进出——脚本上下文、ID、样式等。一切都很好地包含和控制。
但等等,插槽元素的一个积极方面不是它们从文档中带来的给定样式被传递到 Shadow DOM吗?我们有问题了!

所以也许我们不想要文档中那些讨厌的样式。但嘿,至少我们可以在 Shadow DOM中覆盖它们。对吗!?
是的,我们可以! …有时候。
插槽元素设计为具有较弱的特殊性。让我用另一个demo来演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<h1>Slotted elements are weak</h1>

<div class="card-demo">
<h2 slot="heading">This heading slot is Navy</h2>

<p slot="subheading">As defined in the Shadow DOM</p>

<p>A <code>&lt;template&gt;</code> with a <code>shadowrootmode</code> attribute attaches a shadow DOM tree to it's parent element without the need for JavaScript. <br>This text is now purple.</p>
<p>Unless the CSS tab adds styles to the document. These will override the <code>::slotted</code> styles in the Shadow DOM.</p>

<template shadowrootmode="open">
<style>
:host {
margin-block-start: 1em;
padding-block: 1ex;
padding-inline: 3ch;
color: purple;
background: lightBlue;
}
#header {
color: steelBlue;
font-size: 1.3333rem;
font-weight: 300;
}
::slotted(h2) {
color: navy;
}
</style>
<header id="header">
<slot name="subheading"></slot>
<slot name="heading"></slot>
</header>
<slot></slot>
</template>

</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ody {
color: canvasText;
}

h2 {
color: green;
}
p {
color: blue;
}


/* Component based override option */
/*
.card-demo h2 {
color: navy;
}
*/

在这个例子中,我们无法对插槽元素进行样式设置。这是因为文档已经为它们应用了样式。
当我们在::slotted规则中添加一个!important时,我们可以强制使其起作用。即使文档样式也添加了!important::slotted样式仍然有效。

1
2
3
4
5
6
7
8
/* 文档中的样式 */
h2 {
color: green !important;
}
/* Shadow DOM中的插槽样式 */
::slotted(h2) {
color: navy !important; /* 颜色是海军蓝 */
}

这是一个您确实需要使用!important来反转层叠的用例。

然而,这意味着从Shadow DOM中的样式化插槽内容始终需要对每个属性使用!important。请记住,初始样式可能很重要,所以要非常小心,特别是如果您无法控制文档中的内容。对于大多数情况,您应该接受插槽元素保留它们被赋予的样式。只有在真正需要时才覆盖它们。

如果进行兼顾

事实上,我们只有一种方法可以以清晰和包容的方式覆盖Shadow DOM中插槽内容的样式。从文档内部选择阴影元素和插槽元素,并从那里进行样式设置。

1
2
3
.card-demo h2 {
color: navy;
}

我承认在文档中添加这些额外的样式有点奇怪。然而,在大多数情况下,将这些样式与主要样式捆绑在一起是可以接受的。这也允许文档作者在需要时明确应用覆盖样式。
在这样做之前,您应该再次问自己是否需要这些额外的覆盖。经过一些考虑,您可能会发现,您可以在没有增加复杂性的情况下解决问题。

另一种选择

对于我使用Web组件的设计系统组件库的特定用例,我有一个解决方法。在这种情况下,我无法控制也无法了解加载库组件的网页。因此,我有时会添加一些额外的CSS来强化插槽元素。

由于组件已经被明确加载,所以我可以直接将可构建样式表插入到文档中——无需要求开发人员加载额外的样式。Web组件可以替您完成这项工作。

这个演示了如何使用adoptedStyleSheets向文档添加覆盖样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 覆盖文档样式
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
#demodiv {
& h2 {
color: navy;
}
& p {
color: purple;
}
& [slot="subheading"] {
color: steelBlue;
font-size: 1.3333rem;
font-weight: 300;
}
}
`);
document.adoptedStyleSheets.push(sheet);

这有点极端,我不一定推荐这样做。然而,这是一种使用新技术如可构建样式表的有趣方法。

无论您将来是否使用影子DOM,我希望现在插槽对您来说不再是一个谜。