原文链接:Shadow DOM: Styles, 28 AUGUST 2013 on Web Components, Shadow DOM
昨天的博文的内容全是关于你的第一个 Shadow DOM 元素的组织和编码。但我知道一大批围观群众都会问:我们咋加样式啊?!
要讲在 Shadow DOM 上使用 CSS 样式实在是个有趣的大话题,事实上,这个话题太大以至于我准备将其拆成两篇博文来阐述。
如果你想直奔主题,我列出我的这一篇博文 Shadow DOM CSS 小抄以供参考。
今天我们将学习在影子边界(shadow boundary)上使用 CSS 样式,以及如何给影子宿主(shadow hosts)添加样式。
在今天开始之前,我想要感谢 Eric Bidelman 的这篇介绍 Shadow DOM 样式添加的宏文(可以戳中文译版)。本文的大部分都是我对他这篇博文内容的实践。如果有机会的话你一定要去读一下HTLM5 Rocks 关于 Web Components 的全部文章。
我建议你使用 Chrome v33+ 来实验本文的例子,因为 33+ 的 Chrome 对我所描述的这些新特性都有浏览器的原生支持。
机智的读者可能会注意到在文章开始的简介处我使用了一个新的术语——影子边界。影子边界表示分离常规 DOM (与影子 DOM 相对立的“光明” DOM)与 shadow DOM 的壁障。影子边界的主要好处就是防止主 DOM 中的样式泄露到 shadow DOM 中。这就意味着即使你在主文档中有一个针对全部 <h3>
标签的样式选择器,这个样式也不会不经你的允许便影响到 shadow DOM 的元素。
你说栗子呢?诺,来个栗子:
<body> <style> button { font-size: 18px; font-family: '华文行楷'; } </style> <button>我是一个普通的按钮</button> <div></div> <script> var host = document.querySelector('div'); var root = host.createShadowRoot(); root.innerHTML = '<style>button { font-size: 24px; color: blue; } </style>' + '<button>我是一个影子按钮</button>' </script> </body>
我们整了两个按钮,一个是普通的 DOM,另一个是 shadow DOM。注意页面顶部的 <style>
标签指示所有的 button
都要用行楷以及 18px 的字号。
<style> button { font-size: 18px; font-family: '华文行楷'; } </style>
由于影子边界的存在,第二个按钮忽略掉这个样式标签并使用自己的样式。由于我们没有重写 font-family
属性,所以它使用了浏览器默认的字体来实现。
要记住影子边界也保护主文档不受 shadow DOM 样式的侵袭。你可能注意到影子按钮有一个蓝色的color
属性,但是原文档中的按钮还是保持了它默认的显示样式。
这种作用域化(scoping)的特性实在是非常的“额妹子嘤”。我们折腾了这么些年样式表,它的选择范围似乎越来越大。你越来越难以向一个项目中添加新的样式,因为你担心不小心把页面中的那一块搞崩掉。Shadow DOM 提供给我们的样式边界意味着我们终于可以开始用一种更加局部的、特定组件化的方式来考虑和编写我们的 CSS。
我经常把影子宿主想象成一栋建筑物的外表。这栋建筑物的内部有组件的全部运行方式,外面有一个好的门面。 许多情况下你可能会想给这门面调整一下样式,这就轮到 :host
选择器出场了。
<body> <style> .widget { text-align: center; } </style> <div class="widget"> <p>Hello World!</p> </div> <script> var host = document.querySelector('.widget'); var root = host.createShadowRoot(); root.innerHTML = '<style>' + ':host {' + ' border: 2px dashed red;' + ' text-align: left;' + ' font-size: 28px;' + '} ' + '</style>' + '<content></content>'; </script> </body>
尽管给我们的组件添加一个红色边框看似没啥,但这里面可是发生了很多有趣的事情。首先,应用于:host
的样式是继承自 shadow DOM 里的元素的。所以我们的 <p>
标签里的字体大小有 28px。
同时注意到,页面上的样式可以设置 :host
中的 text-align
的文本对齐方式为居中。:host
的选择器的优先级被设定为低于页面选择器的优先级,所以如果有需要的话它可以轻松的被页面重写样式。在这个例子里页面样式 .widget
击败了影子样式 :host
。
由于 :host
是伪类选择器(Pseudo Selector),我们可以将其应用于多个标签上来改变我们组件的外观。我们举一个其他的栗子来证明这一点。
<body> <p>我的段落</p> <div>我的 Div</div> <button>我的按钮</button> <!-- Our template --> <template class="shadow-template"> <style> :host(p) { color: blue; } :host(div) { color: green; } :host(button) { color: red; } :host(*) { font-size: 24px; } </style> <content select=""></content> </template> <script> // 给每一个元素创建一个影子根 var root1 = document.querySelector('p').createShadowRoot(); var root2 = document.querySelector('div').createShadowRoot(); var root3 = document.querySelector('button').createShadowRoot(); // 对于每一个影子根使用同一个模板 var template = document.querySelector('.shadow-template'); // 把每一个模板嵌入影子根中,注意一下不同的 :host 样式对显示效果的影响 root1.appendChild(document.importNode(template.content, true)); root2.appendChild(document.importNode(template.content, true)); root3.appendChild(document.importNode(template.content, true)); </script> </body>
由于模板标签在使用上要更简单一些,因此我在这个例子中改用模板标签来操作 Shadow DOM。
在上面的例子中可以看到,我们可以利用 :host
选择器来改变我们组件的某一个特定标签样式。我们还可以根据类名、ID、属性等等来进行匹配选择——任何有效的 CSS 选择器都可以正常工作。
比方说,如果你想写一个自适应的组件,你可以在 :host
中写各种诸如 .widget-fixed
、.widget-flex
、.widget-fluid
的样式,或者在表单元素的 :host
中写 .valid
和 .error
样式。
通过使用 *
选择器,我们可以创造应用于全部 :host
元素的默认的样式,正如在这个例子中我们设置所有组件的 font-size
为 24px。通过这一方式你可以构建组件的基本外观,然后在通过不同方式的选择器给你的组件增光添彩。
那我们怎么基于宿主元素的父元素对它构建不同的主题呢?嚯嚯,我们有一个专门实现主题化的选择器!
<body> <div class="serious"> <p class="serious-widget"> 大家好我十分的严肃 </p> </div> <div class="playful"> <p class="playful-widget"> 漂亮的小云彩效果…… </p> </div> <template class="widget-template"> <style> :host-context(.serious) { width: 250px; height: 50px; padding: 50px; font-family: '微软雅黑'; font-weight: bold; font-size: 24px; color: black; background: tomato; } :host-context(.playful) { width: 250px; height: 50px; padding: 50px; font-family: '华文行楷'; font-size: 24px; color: white; background: deepskyblue; } </style> <content></content> </template> <script> var root1 = document.querySelector('.serious-widget').createShadowRoot(); var root2 = document.querySelector('.playful-widget').createShadowRoot(); var template = document.querySelector('.widget-template'); root1.appendChild(document.importNode(template.content, true)); root2.appendChild(document.importNode(template.content, true)); </script> </body>
使用 :host-context()
的语法我们可以基于内容元素修改我们组件的外观。这实在太简洁啦!我确信你曾用过子类选择器,例如 .parent > .child
这样的,但你是否曾梦想有例如 .parent < .child
的这样一个父类选择器?现在你的梦想成真啦,当然这仅限于使用 shadow DOM。我在想有一天我们是不是能看到这个语法也能在 CSS 中出现呢?
:host
标签最好用的地方之一就是设置状态的样式,例如 :hover
和 :active
。例如,我们想在按钮被用户鼠标覆盖的时候上加一个绿色的边框,好办!
<body> <button>我的按钮</button> <template class="button-template"> <style> :host { font-size: 18px; cursor: pointer; } :host(:hover) { border: 2px solid green; } </style> <content></content> </template> <script> var host = document.querySelector('button'); var root = host.createShadowRoot(); var template = document.querySelector('.button-template'); root.appendChild(template.content.cloneNode(true)); </script> </body>
这没啥特别的,就是希望你多想一些场景。你还想到了其他你可以使用的场景么?
关于 Shadow DOM 的样式我还想多说点,但是今天就到这里吧,我们明天继续。和往常一样欢迎来我的 twitter 艾特我或者给我留言,感谢阅读!
译者注:
由于新概念有点多,列出一些参考博文和自己的见解,以飨读者。
模板标签,HTML5Rocks的文章中译本,结合这篇以及文中给出的作者自己博文的链接就能大概了解模板标签的特性。这个标签目前在非 IE 的浏览器下都得到支持,主要有四个特性:
本文中利用模板为 shadow DOM 填充内容,省去了通过 JS 加载的麻烦。注意文中使用.content
属性来导出模板的内容,并通过 importNode
方式对模板进行了深拷贝。
惰性:在使用前不会被渲染;
无副作用:在使用前,模板内部的各种脚本不会运行、图像不会加载等;
内容不可见:模板的内容不存在于文档中,使用选择器无法获取;
可被放置于任意位置:即使是 HTML 解析器不允许出现的位置,例如作为 <select>
的子元素。
:host-context
的作用,这个选择器的实际用法是用来选择影子宿主的祖先元素的,最大的用处大概就是主题的设置。比方我的组件有各种状态,但是很多情况用户实际操作的可能是组件外部的父元素,这个选择器的目的就是通过更改父元素的样式状态就能完成组件状态的变化。