shadow-dom 是什么
顾名思义,
shadow-dom
,直译的话就是
影子dom
?我觉得可以理解为潜藏在黑暗中的 DOM 结构,也就是我们无法直接控制操纵的 DOM 结构。
前端同学经常用开发者工具的话,查看 DOM 结构的时候,肯定看到过下面这样的结构:
这里的
#shadow-root
所包含的内容其实就是所谓的
shadow-dom
。
shadow-dom
其实是浏览器的一种能力,它允许在浏览器渲染文档(document)的时候向其中的 Dom 结构中插入一棵 DOM 元素子树,但是特殊的是,这棵子树(shadow-dom)并不在主 DOM 树中。
举个栗子,也是最常见的例子,
<video>
标签,我们创建在页面上创建一个空白的
video
标签:
<video id='test'></video>
查看 DOM 结构如下:
虽然我们创建的是一个空标签,但是在这个空标签内部,存在一个
shadow-dom
,点开
shadow-dom
可以看到内有乾坤,大有内容。其实这内部的具体内容,就是
<video>
的具体实现。
shadow-dom 结构示意
再用一幅图总结一下:
document
这个很好理解,就是我们的正常文档 document 。
shadow host
对于一个内部有
shadow-dom
的元素而言,它必然需要一个宿主元素,对于上面的例子而言,
<video>
标签,就是 shadow-dom 的宿主元素。
shadow-root
通过
createShadowRoot
(下文会提及) 返回的文档片段被称为 shadow-root 。它和它的后代元素,都将对用户隐藏,但是它们是实际存在的,在 chrome 中,我们可以通常审查元素去查看它们的具体 DOM 实现。
在
<video>
中,例如暂停,播放,音量控制,全屏按钮,进度条等都是 shadow-root 的后代。它们工作时会显示在屏幕上,但他们的 DOM 结构对用户是不可见的。
contents
就是上述所说的
<video>
中各子组件的 DOM 的具体实现。
为什么需要 shadow-dom
为什么需要有这种结构呢?
Shadow-dom 是游离在 DOM 树之外的节点树,但是他的创建基于普通 DOM 元素(非 document),并且创建后的 Shadow-dom 节点可以从界面上直观的看到。更重要的是,Shadow-dom 具有良好的密封性。
这是浏览器提供的一种“封装”功能,提供了一种强大的技术去隐藏一些实现细节。什么意思呢?以
w3c
上的一个
<video>
例子为例,我们仅仅是填写了一个空白的标签,再加上
src
属性里填上视频地址,就可以播放视频了:
我们仅仅填写了一行代码,却拥有比这行代码更多的功能,譬如暂停,播放,音量控制,全屏按钮,进度条等等。
这些功能具体的 DOM 实现,其实都在
shadow-dom
中:
浏览器的开发者们意识到作为前端开发者,引用一个
<video>
标签的时候,每次还要写入一大堆 DOM 去控制控件的表现和行为,既不简洁也很困难。所以他们界定了这样一个界限,界定了哪些是你可以访问的,哪些实现细节是访问不到的。
那些不希望我们访问到的细节,则封装在了
shadow-dom
中。然而,浏览器本身却可以随意跨越这个边界。设置这样一个边界之后,浏览器的开发者们就可以在我们看不见的地方使用熟悉的web技术、同样的HTML元素去创建更多的功能,而不是像我们一样要在页面上用div和span来堆砌这些元素。
如何控制 shadow-dom
既然是浏览器开发者有意隐藏起来的 DOM 结构,那么我们是否可以控制内部的 DOM 结构呢?并非完全不可以,还是有一些方法使得我们可以控制
shadow-dom
内的一些表现。
使用伪元素控制 shadow-dom 样式
这里我们要使用到伪元素,通过伪元素,我们可以控制
shadow-dom
中 DOM 结构的样式。
在 chrome 下,查看
shadow-dom
结构(如果无法看到shadow-dom需要手动打开),可以看到每个结点都加上了一个 pesudo 属性:
有了这些属性,我们可以通过伪元素的方式控制他们,譬如在一些场景下 video 标签的控制条不会自动隐藏或自动显示,可以通过伪元素指定默认显隐方式:
如果你在 chrome 浏览器下阅读本文,从上面的 codePen 可以看到,我使用伪元素修改了 video 控件条的底色为粉红色 deeppink。
不幸的是,上面的控制方式只适用于 chrome 浏览器,虽然大部分现代浏览器已经支持
shadow-dom
,但是能够审查
shadow-dom
内部 DOM 元素的只有
chrome
浏览器,其他浏览器仍会把这些细节隐藏。
使用 Javascript 创建一个 shadow-dom 元素
我们也可以通常 Javascript 创建
shadow-dom
,实现各类功能的封装,主要通过:
HTMLElement.prototype.createShadowRoot =
HTMLElement.prototype.createShadowRoot ||
HTMLElement.prototype.webkitCreateShadowRoot ||
function() {};