OK ~ ~ ~
很合适的一个例子就是
video标签包裹着source标签,但查看页面显示时会发现并没有那么简单,
视频播放器本身有播放/暂停按钮、进度条、视频时间显示、音量控制以及播放时的一个全屏切换按钮。那这些东西都是哪里来的?
实际上,各浏览器内置了播放器组件代码。打开chrome开发者工具,进入setting:
在preference的Elements之后,选中“show user agent shadow DOM”
这时候再回到DOM结构来看一下:
看到标灰的#shadow-root了吗?这里就是所有视频播放器控制组件的所在之处。浏览器之所以将其置灰,是为了表明这部分是在 shadow DOM里,对于页面的其他部分来说它是不可用的。这里的“不可用”意味着你写的CSS选择器和 JavaScript 代码都不会影响到这部分内容。实际上,让
再回到最初的问题上,什么是Shadow DOM?
简而言之,Shadow DOM 是一个 HTML 的新规范,其允许开发者封装自己的 HTML 标签、CSS 样式和 JavaScript 代码。这使得开发人员可以创建诸如
在学习Shadow DOM 基础之前,得了解一些基本的概念。
我们编写的HTML代码在浏览器中会转化成DOM,每个元素是一个节点,一段完整的HTML就形成了节点数。Shadow DOM 的独特之处在于它允许我们创建自己的节点树,这种节点树被称为 寄生树(shadow trees) 。寄生树对其中的内容进行了封装,有选择性的进行渲染。这就意味着我们可以插入文本、重新安排内容、添加样式等等。举个栗子:
Hello World
打开开发者工具看一下:
以上代码最终显示的并不是标签中写的 “Hello World”,这个文本内容被我们通过一个寄生树替换掉了。
要创建一个寄生树,我们首先要指定一个节点担任 宿主(shadow host) 。在这个例子里,我们将 .widget
当做我们的宿主。然后我们给宿主添加一个称作 寄生根(shadow root) 的新节点。寄生根 作为 寄生树 的第一个节点,其他的节点都是它的子节点。
再来一个例子:
Hello World
在原来代码上新增两个元素,来看看结果:
Shadow DOM 的操作与普通的DOM 的操作区别不大,仍然可以使用 appendChild
和 insertBefore
来将子节点添加到父节点上。
这里细心的会发现,原生DOM节点div 标签中的文本还是没有渲染出来,那如果想要这里的文本渲染出来要怎么做呢?
使用新标签
再来一个例子:
pikaqiu
A wild appeared!
结果如下:
使用
标签,我们创建了一个 插入点(insertion point) ,其将类名为 pokemon
的 div 中的文本 投射 出来,使之能够在我们的寄生节点 中展示。插入点十分强大,它允许我们在不改变源代码的情况下改变渲染顺序,这也意味着我们可以对要呈现的内容进行选择。
既然说到了内容选择,那就来一个例子:
王
小二
苏州
学生
我叫王小二,王是王小二的二,二是二百五的五
- 姓
- 名
- 城市
- 身份
在这个例子中我们创建了一个非常简单的简历组件。因为每个定义的字段都需要特定的内容,我们必须告诉
标签有选择性的插入内容。为了做到这一点,我们使用 select
属性。 select
属性使用 CSS 选择器来选取想要展示的内容。
譬如说,
会在宿主里寻找任何样式名称为 .last-name
的元素。如果找到一个匹配的元素,其就会将这个元素渲染到 shadow DOM 中对应的
标签中去。当然,我们可以发现,最终的结果是按照 template 中代码顺序展示的,那么如果我们想要改变渲染的顺序,就可以在不动宿主内容的前提下对展示的效果进行了改变。
如果你实践了上面的代码,你就发现页面显示的非常简单甚至丑陋,因为我们并没有添加任何样式,那如何在 Shadow DOM 上使用 CSS 样式呢?
这里要介绍新的概念, 影子边界(shadow boundary) 以及 影子宿主(shadow hosts)。
所谓影子边界,就是分离 常规 DOM 与 shadow DOM 的壁障。影子边界的主要好处就是防止 常规 DOM 中的样式泄露到 shadow DOM 中。这就意味着即使你在主文档中有一个针对全部 标签的样式选择器,这个样式也不会不经允许的影响到 shadow DOM 的元素。
如下例:
写了两个按钮,一个是普通的 DOM,另一个是 shadow DOM。<style>
标签规定所有的 button
都要用花体字以及 18px 的字号。由于影子边界的存在,第二个按钮忽略掉这个样式标签并使用自己的样式。由于我们没有重写 font-family
属性,所以它使用了浏览器默认的 sans serif 字体来实现。同时,影子边界也保护主文档不受 shadow DOM 样式的侵袭。你可能注意到影子按钮有一个蓝色的 color
属性,但是原文档中的按钮还是保持了它默认的显示样式。
影子边界大概就这样,下面讲一下影子宿主怎么加样式。
结果是给组件添加一个红色边框,这看似没啥,但其中可是发生了很多有趣的事情(有趣个屁哟)。
:host
伪类选择器首先,应用于 :host
的样式是继承自 shadow DOM 里的元素的。所以我们的 标签里的字体大小有 28px。同时注意到,页面上的样式可以设置
:host
中的 text-align
的文本对齐方式为居中。 :host
的选择器的优先级被设定为低于页面选择器的优先级,所以如果有需要的话它可以轻松的被页面重写样式。在这个例子里页面样式 .widget
击败了影子样式 :host
。
总的来说,:host
是一个伪类选择器,它匹配
s shadow DOM 的“host”元素,即
元素本身。由于 :host
是 伪类选择器 ,我们可以将其应用于多个标签上来改变我们组件的外观。例如:
My Paragraph
My Div
从上面代码中可以看到,我们可以利用 :host
选择器来改变我们组件的某一个特定标签样式。我们还可以根据类名、ID、属性等等来进行匹配选择——任何有效的 CSS 选择器都可以正常工作。
比方说,如果你想写一个自适应的组件,你可以在 :host
中写各种诸如 .widget-fixed
、 .widget-flex
、 .widget-fluid
的样式,或者在表单元素的 :host
中写 .valid
和 .error
样式。
通过使用 *
选择器,我们可以创造应用于全部 :host
元素的默认的样式,正如在这个例子中我们设置所有组件的 font-size
为 24px。通过这一方式你可以构建组件的基本外观,然后在通过不同方式的选择器给你的组件增光添彩。