最近一个内部系统可视化设计器的布局在用户升级 chrome 到 53 版本之后,里面的坑位增加按钮消失了,经过排查发现,坑位的标签使用了slot
,而坑位中的按钮是通过 css 的 empty 伪类实现的,即坑位为空时,出现增加按钮,但现在为什么突然无效了呢?
把slot
的名字换成其他标签名,发现样式顿时生效,于是猜测是slot
作为特殊标签被 chrome 设置了特定的行为,经过查阅资料发现slot
是 shadow dom 中的内置标签。
现在我们来了解下 shadow dom 以及与其相关的两个特殊标签 template 和 slot 。
何为shadow dom
shadow dom,顾名思义,影子节点,它是 web components 规范的一个子集,主要为了解决dom对象的封装,通常的dom,其js行为和css样式极易受到别的代码的干扰,但shadow dom规定了只有其宿主才可定义其表现,外部的api是无法获取到shadow dom中的东西。
由于shadow dom是影子元素,因此其必须捆绑一个宿主元素,宿主元素事实上成为了“傀儡”,宿主元素的内容将被隐藏,而shadow dom的内容将展示出来,以下是一个例子:
html:
<div id="con">
没什么卵用的文字
</div>
js:
var host = document.querySelector('#con');
var root = host.attachShadow({mode:'open'});//为宿主附加一个影子元素
root.innerHTML = "我来自shadow dom";//为影响元素附上内容,shadow dom的api和普通dom的大致相同
最终效果:
我来自shadow dom
可以看到,宿主的内容确实被掩盖了,然而通过chrome的devtools,可以看到宿主的原内容以及背后的shadow dom:
shadow dom中的template
前面说了,shadow dom可以实现dom的隔离,比如样式的封装,那么如何实现呢?shadow规定了一种名为template
的标签,这种标签类似我们经常用的<script type='tpl'>
,它不会被解析为dom树的一部分,template的内容可以被塞入到shadow dom中并且反复利用,在template中可以设置style,但只对这个template中的元素有效,看下示例:
html:
<style>
span {
background-color:blue;/*设置页面所有span背景为蓝色,然而对shadow dom没什么卵用*/
}
</style>
<div id="con">
没什么卵用的文字
</div>
<template id="tpl">
<style>
span {
color:red;
}
</style>
<span>hello world</span>
</template>
js:
var host = document.querySelector('#con');
var root = host.attachShadow({mode:'open'});
var con = document.getElementById("tpl").content.cloneNode(true);
root.appendChild(con);
效果截图:
<span style='color:red'>hello world</span>
可以看到,template
的内容被塞入到宿主,并且其文案被设置为红色,而body 中对 span 设置为蓝色背景却没有生效;另外这里要注意document.getElementById("tpl").content
中的content
属性,它是template标签的特有属性,你可以通过嗅探该属性来判断浏览器是否支持shadow dom和template
标签。
shadow dom的slot标签
由于shadow dom的内容会掩盖宿主的内容,那么现在问题来了,我就是想把宿主的内容显示出来怎么办?
最新的shadow dom草案支持了一个叫slot标签的东西,slot是一个插槽,一个坑位,可以在template中定义坑位,然后宿主中的内容可以标记属于哪一个坑位,这样一个萝卜一个坑,宿主的内容就会被正确地插入到template所标记的位置去,还是来看一个例子:
html:
<div id="con">
没什么卵用的文字
<span slot="main1">
坑位1
</span>
<span slot="main2">
坑位2
</span>
没什么卵用的文字 </div>
<template id="tpl">tpl begin
<slot name="main1">
</slot>
<slot name="main2">
</slot>
tpl end
</template>
js:
var host = document.querySelector('#con');
var root = host.attachShadow({mode:'open'});
var con = document.getElementById("tpl").content.cloneNode(true);
root.appendChild(con);
最终的效果是:
tpl begin 坑位1 坑位2 tpl end
可以看到,宿主中的两个span分别插入到了其标记的slot坑位中。在slot出现之前,仍然可以实现类似的功能,只不过标签名叫content。
结语
现在我们来解释一下文章开头出现的问题的原因,由于 slot标签仅仅是一个占位符而已,其最终会被宿主标记了该位置的内容替换(注意是替换,而不是插入),因此没必要对slot标签设置样式,这就是为啥chrome 53忽略其样式的原因,无独有偶,最新版的ios 10默认浏览器也会隐藏slot,因为slot中并不需要显示任何东西。
借鉴vue的思想,我们在编译阶段把slot编译成div,从而解决了这个问题,顺便提醒一下,shadow dom是非常新的技术,目前还处在试验阶段,而且标准还不确定,请不要在生产环境使用。
网友评论