神奇的Shadow DOM

FgHV3-D9eRTz1L6CNQht0jo6a4lt

你有好奇过这个问题吗,为什么只用video标签包裹着source标签,就可以完成一系列视频功能:播放/暂停按钮、进度条、视频时间显示、音量控制等等?既然 DOM 源码这么干净,你有想过实现这些组件的代码是从哪儿来的吗?

1. 简介

Shadow DOM它允许在文档(document)渲染时插入一棵DOM元素子树,但是这棵子树不在主DOM树中。

因此开发者可利用Shadow DOM 封装自己的 HTML 标签、CSS 样式和 JavaScript 代码。

看一个简单的video:

页面完成了,在浏览器chrome中打开,然后打开 Chrome 的开发者工具,点击右上角的“Settings”按钮,勾选“Show user agent shadow DOM”。

Fq09xC-qD04z43WYyVb4P0tPC-NC

浏览器截图:

FhgQ2ofckuyoCPD8RlAd3INcGRAx

#shadow-root称为影子根,可以看到它在video里面,换句话说,#shadow-root寄生在video上,所以video此时称为影子宿主。可以看到上图有两个#shadow-root,这是因为#shadow-root可以嵌套,形成节点树,即称为影子树(shadow trees)。影子树对其中的内容进行了封装,有选择性的进行渲染。这就意味着我们可以插入文本、重新安排内容、添加样式等等。如下所示:

FoF1XzsX8voCu3Gh9mJKBXkHeQDC

2. 怎样创建Shadow DOM

使用createShadowRoot()来创建Shadow DOM,并赋值给一个变量,然后添加元素给变量即可。

创建Shadow DOM

有没有注意到.shadowroot_son的样式color: #f00;不生效?!那是因为影子宿主和影子根之间存在影子边界(shadow boundary),影子边界保证主 DOM写的 CSS 选择器和 JavaScript 代码都不会影响到Shadow DOM,当然也保护主文档不受 shadow DOM 样式的侵袭。

3. 想要渲染影子宿主里的内容,那该怎么玩?

需要完成此项任务,需要两个利器:<content><template>

3.1 <content>

通过 <content> 标签把来自主文档并添加到 shadow DOM 的内容被称为分布节点。

<content>的select属性告诉<content>标签有选择性的插入内容。select 属性使用 CSS 选择器来选取想要展示的内容,选择器包括类选择器、元素选择器等。

3.2 <template>

目前的模板HTML做法通常是在<script> 中嵌入模板HTML,让内部的HTML标签按照字符串处理的,从而使得内容不显示:

<template>元素的出现旨在让HTML模板变得更加标准与规范。

<template>在使用前不会被渲染,不会执行加载等操作,也能够实现隐藏标签内容,而且位置任意性,可以在<head>中,也可以在<body>或者<frameset>中。

3.3 实例

通过以上对 <content><template>的简单了解,我们来通过一个实例加深理解:

浏览器截图:

FswaNQUdHF17XfTya9wO4ZnKIryN

我们来看一下下面三个属性的用途:

贪心插入点:如果把select=”.shadowhost_content1”改成select=””或者select=”*”,那么会有不一样的结果。因为贪心选择器放在了模板的第一个,他会将所有内容都抓取,不给其他select 选择器留一点内容。浏览器截图如下:

FgnGjHYb8sI1_jns_CViHrsXgc6n

4. 关于样式

4.1 宿主样式:host

在shadow DOM中利用:host定义宿主的样式,当然用户可以在主文档中覆盖这个样式。

:host 是伪类选择器(Pseudo Selector),:host或者 :host(*)是默认给所有的宿主添加样式,或者单独给一个宿主添加样式,即通过:host(x),x可以是宿主的标签或者类选择器等。

另外:host还可以配合:hover、:active等状态来设置样式,如:

4.2 ::shadow

原则上来说,影子边界保证主 DOM写的 CSS 选择器和 JavaScript 代码都不会影响到Shadow DOM。
但你可能会想打破影子边界的所谓保证,主文档能够给Shadow DOM添加一些样式,这时可以使用::shadow。

4.3 /deep/

::shadow 选择器的一个缺陷是他只能穿透一层影子边界,如果你在一个影子树中嵌套了多个影子树,那么使用 /deep/ 。

4.4 ::content

还记得什么叫分布节点吗?通过 <content> 标签把来自主文档并添加到 shadow DOM 的内容被称为分布节点。

分布节点的样式渲染需要用到 ::content。即使分布节点为em标签,直接写 em {} 不生效,应该写成::content > em {}。

4.5 实例

浏览器截图如下:

FojZYP5WW1JAGN-3fyW9jnFLGiuN

5. JavaScript

5.1 重定向

Shadow DOM 里的 JS 与传统的 JS 一个真正不同的点在于事件调度(event dispatching)。要记住的一点是:原来绑定在 shadow DOM 节点中的事件被重定向了,所以他们看起来像绑定在影子宿主上一样。

当你点击“shadow text”的输入框时控制台却输出了宿主元素(就是 #host)的 id 。这是因为影子节点上的事件必须重定向,否则这将破坏封装性。

分布节点来自原有 DOM 结构,没必要重定向。

分别单击每个输入框,控制台打印截图如下:

FuPYA7rXQnnK78gC75QnvhRkzpqN

5.2 被阻塞的事件(Blocked Events)

事件abort、 error、 select 、change 、load 、reset 、resize 、scroll 、selectstart不会进行重定向而是直接被干掉,因此事件不能冒泡到文档中,事件监听重定向至文档,因此无法监听到这一事件。

把上面的监听事件click改成select,即改成:

分别双击每个输入框,你会发现,shadow text的输入框没有打印,就是没有发生select事件。

FpPbkUQKdtodVkllgCc3K4-5miO

6. 兼容性

FpSHZHqQtxYyUIO2vdUCRaVnJyaA

FuAGMDQSFdBe_xEdSolSbU-avlpd

看上去只能在chrome中愉快地玩耍。

webcomponents.js使得Shadow DOM在非 native 支持的浏览器上实现。

7. 参考链接

A Guide to Web Components
Shadow DOM系列文章
HTML5 <template>标签元素简介

1 1 收藏 评论

可能感兴趣的话题



直接登录
跳到底部
返回顶部