黄琼:如何使用shadow DOM生成Web组件?


责编 | 韩楠

约  1898  字 | 4 分钟阅读





前言


之前我曾做过这样 一篇技术分享 ,侧重讲了如何去自定义html标签,有感兴趣的朋友可以看下。与此相关的,其实,里面还遗留了一个重要的问题点,我觉得很有必要单拎出来,今天就跟大家一起分享下。


就是如何将我们这个自定义的html标签封装好,可以做到像原生的video那样,自己的样式、结构以及js行为都不被外界影响,这样就不会因为外界的错误而造成组件bug了。


这一篇文章,我们使用shadow dom来封装这个自定义标签,实现与外界隔离的效果。




shadow DOM是什么


大家可能没听过shadow DOM,但是DOM应该都听过吧。那什么是DOM呢?MDN对于它的定义是这样的。


这里粗略翻译一下:就是说DOM就是web documents的一个编程接口,它可以将document转化成节点跟对象,这样编程语言,才可以去跟页面上的HTML元素进行交互,包括改变它的结构、样式和内容。


我个人的理解是:


浏览器会先将HTML结构解析为一个DOM树,每一个html元素都是这个对象里的一个DOM节点,然后DOM提供用来操作(包括增,删,改)HTML元素的方法,让用户可以操作里面所有的节点。比如,我们经常用到的getElementById 就是DOM给我们提供的通过id获取html元素的方法。


这套用来解析html结构,并且提供操作他们的API标准就是HTML DOM。


其实简单来说,你不管它的概念就是把它当成中介就行了。有了这个中介,我们才可以去获取和操作到页面的HTML元素。


这样以此类推,shadow DOM,就是用来操作 “shadom DOM 节点” 的 API 标准。 这个“shadom DOM节点”,就是我们今天的重点 ,它跟常规的DOM节点不同。


它最大的特点就是与常规DOM节点是隔离的,用普通的DOM API,比如getElementById去拿一个shadom DOM节点是拿不到的。


下面举一个例子:


前面说过浏览器会把html解析成DOM树,这里我们假设平平无奇的DOM树长这样。



接着我们再构建 shadow DOM 树,这里的 shadow 的根节点就叫 shadow root。它由一个shadow boundary 的虚拟边界跟外界隔着。



构建好之后,就需要挂到DOM树上的某个节点,这个被挂载的节点叫  shadow host。这里我们选取最右边这个DOM节点作为挂载的节点挂上去。你可以理解成像纽扣一样扣上去。



如此一来,整个树就合成了,合成之后的DOM树就长这样。我们可以看到,此时这颗DOM树有绿色的常规DOM节点,以及粉色的shadow DOM节点。



接下来看看代码要如何实现这样一颗树。




使用shadow DOM


前面说过,shadow DOM树必须要挂载在一个常规的DOM节点(shadow host)上,这里我们用 Element.attachShadow() 来创建shaodw DOM 树的根节点 shadow root。这里的Element 指的就是 shadow host。



参数mode,可以传open跟closed。


区别在于使用open的话,你可以用  let myShadowDom = myCustomElem.shadowRoot;  拿到shadow root,而closed不行的。

但是,通过一些hack的方式硬要拿也是可以拿到的,所以实际使用中我们就没必要用closed,open就够用了。


下面,来给我们之前自定义好的HTML标签,加上这个shadow root节点。以下,就是我们上一节完成的 标签。



我们的任务,就是把这个自定义标签里的DOM节点,在这里就是这个

及其子节点,变成shadow DOM节点。



也很简单,首先创建shadow DOM树的根节点shadow root,然后构建到shadow DOM树的节点之后,再挂载到shadow root下即可。


完整代码如下:



到这里我们就完成啦。可以看到效果图里,

Hey, it's me

已经变成shadow DOM节点了。



接着,我们试试用  document.getElementsByTagName('h1'), 去获取这个  

  标签,会发现拿不到,符合预期。


如果我们真的要去操作,那就得用 shadow DOM 的 API。也就是说,常规的用DOM API的操作都是不可能会影响到shadow DOM的。


这就是实现隔离的原理




style shadow DOM


创建好shadow DOM树之后,接下来我们给它点样式装修一下。这里跟常规的添加样式的方法一样,有两种方式,一种是使用