shadow DOM的使用
web components的一个重要方面就是封装,这样才能保持标签结构、样式、行为和页面中的其它代码隔离。Shadow DOM API就是封装很关键的一部分,它为元素提供了隐藏、隔离DOM的方式。
注:Shadow DOM被Firefox >=63, Chrome, Opera, and Safari,Edge >=75支持。
概览
Shadow DOM允许你在常规的DOM树中隐藏Shadow DOM,Shadow DOM的根节点是shadow root,shadow root里可以附加任何DOM元素,和常规DOM没有区别。
下面我们介绍几个shadow DOM的术语:
- Shadow host(宿主):附加了shadow DOM的常规DOM
- Shadow tree:shadow DOM里面的DOM树
- Shadow boundary(边界):shadow DOM结束,常规DOM开始的位置
- Shadow root:Shadow tree的根节点
你可以像操作常规DOM一样操作shadow DOM里面的DOM。比如添加子节点、设置属性、样式,或者在shadow DOM添加<style>
标签为整个shadow DOM设置样式。shadow DOM的不同之处在于,shadow DOM里面所有的代码操作都不会影响外面,所以对封装很有帮助。
shadow DOM其实不是新东西,在浏览器中其实已经使用很长时间了,浏览器使用它来封装内置DOM元素的结构,比如<video>
元素,在它的shadow DOM里面就包含了一系列的button以及其一些其他控件。shadow DOM规范的制定可以让你封装并操作你自定义的元素。
基本使用
你可以使用Element.attachShadow()
方法为任何元素附加shadow root。它的参数是一个对象,这个对象只有一个设置mode,值为open或closed。
1 | let shadow = elementRef.attachShadow({mode: 'open'}); |
open意味着你可以在页面中使用JavaScript访问shadow DOM,比如使用Element.shadowRoot属性。
1 | let myShadowDom = myCustomElem.shadowRoot; |
如果mode=closed,你是无法在shadow DOM外面获取到shadow root的,myCustomElem.shadowRoot会返回null。比如,内置的<video>
元素,它的设置就是mode=closed,所以video.shadowRoot返回的就是null。
如果你想让shadow DOM封装的自定义元素成为应用的一部分,可以自由访问,你需要设置mode=open。
1 | let shadow = this.attachShadow({mode: 'open'}); |
操作shadow DOM和操作常规DOM是相似的,你可以使用相同的DOM API。
1 | let para = document.createElement('p'); |
简单例子
现在我们通过一个简单例子来演示一下如果使用shadow DOM来封装一个自定义元素——<popup-info>
。这个元素有一张图片和一段文本,当focus在上面的时候,它会提示一些信息。
我们在JS文件中定义一个叫PopUpInfo的class,它继承了HTMLElement。
1 | class PopUpInfo extends HTMLElement { |
然后,创建shadow root
1 | class PopUpInfo extends HTMLElement { |
创建shadow DOM的结构
1 | class PopUpInfo extends HTMLElement { |
接下来,我们创建一个<style>
标签为shadow DOM设置一些样式
1 | // Create some CSS to apply to the shadow dom |
将shadow DOM添加到shadow root中
1 | // attach the created elements to the shadow dom |
我们使用customElements.define定义这个新元素。
1 | // Define the new element |
最后就可以在页面中使用它了
1 | <popup-info |
内部样式vs外部样式
在例子中我们使用<style/>
给Shadow DOM设置样式,不过更好的方式可能是使用<link>
引入外部样式。
1 | // Apply external styles to the shadow dom |
注:由于link的样式需要加载,所以可能会出现FOUC(flash of unstyled content )。
许多现代浏览器对<style>
做了优化。如果是相同的节点,<style>
可以克隆一份。如果是相同的内容,可以复用相同的样式。由于这些优化外部样式和内部样式的性能已经很接近了。