基于Slot的Shadow DOM API

这是webkitBlog上一篇文章的翻译,原文链接

很高兴宣布我们已经对slot-base Shadow DOM API提供基本的支持。最新支持它的浏览器是nightly builds of WebKit,Shadow DOM草案最早由Google推荐,意在支持可重用的网络组件,具体的说:在某个DOM节点的同一级插入一些特殊的DOM节点,这些插入的DOM节点被叫做Shadow DOM,它们可以被渲染,且不改变原有DOM的结构(Shadow DOM, in particular, provides a lightweight encapsulation for DOM trees by allowing a creation of a parallel tree on an element called a “shadow tree” that replaces the rendering of the element without modifying the underlying DOM tree.)。因为一个Shadow DOM并不是同一个传统意义上的DOM节点,所以,你用querySelector等API无法访问到它,也就不需要担心不小心改变了它内部的结构。Shadow DOM中的style样式也是有作用域的,不要当心SD中的样式污染了外部样式,反之亦然。

隔离样式

运用Shadow DOM的一个首要作用就是进行样式隔离。我们首先来看一个进度条的例子。

<style>
.progress { position: relative; border: solid 1px #000; padding: 1px; width: 100px; height: 1rem; }
.progress > .bar { background: #9cf; height: 100%; }
.progress > .label { position: absolute; top: 0; left: 0; width: 100%;
    text-align: center; font-size: 0.8rem; line-height: 1.1rem; }
</style>
<template id="progress-bar-template">
    <div class="progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
        <div class="bar"></div>
        <div class="label">0%</div>
    </div>
</template>
<script>
function createProgressBar() {
    var fragment = document.getElementById('progress-bar-template').content.cloneNode(true);
    var progressBar = fragment.querySelector('div');
    progressBar.updateProgress = function (newPercentage) {
        this.setAttribute('aria-valuenow', newPercentage);
        this.querySelector('.label').textContent = newPercentage + '%';
        this.querySelector('.bar').style.width = newPercentage + '%';
    }
    return progressBar;
}
</script>

请注意template标签的使用,它允许使用者定义并且引入模板,这是在WebKit中实现的一个Web组件,已经写进了HTML5标准。一个template标签能够出现在html文档中的任何位置(比如table标签和tr标签之间)。template中的内容是惰性的,里面的script标签、img标签是不会自动加载的。以上的代码中,我们定义了一个组件:progressBar,想要在页面中插入这个组件,只需要简单的几句js:

var progressBar = createProgressBar();
container.appendChild(progressBar);
...
progressBar.updateProgress(10);

然后你将得到:


这样引入一个控件是很方便的,但是有个问题:如果文档的其他地方也有class="progress"的节点,那么我们控件的样式将污染它。

<section class="project">
    <p class="progress">Pending an approval</p>
</section>

类似的,外部样式也会影响到控件的样式:

<style>
.label { color: red; }
</style>

一般情况下,我们可以为控件定义一个特殊的类型(比如.custom-progressbar)来避免样式冲突,而Shadow DOM提供了一个更加优雅的解决方案。
想法是为外部div引入一个“包含层(encapsulation layer)”,包含层之类的样式不会影响外部的样式,外部的使用者也看不见包含层之类的实现。要做到这些,我们首先在外部div上调用attachShadow({model:'close'})创建一个ShadowRoot,然后在这个ShadowRoot下层添加任何我想要的内容。我们说这个外部div“掌管(host)”了它的ShadowRoot,看看下面的代码:

<template id="progress-bar-template">
    <style>
        .progress { position: relative; border: solid 1px #000; padding: 1px; width: 100px; height: 1rem; }
        .progress > .bar { background: #9cf; height: 100%; }
        .progress > .label { position: absolute; top: 0; left: 0; width: 100%;
            text-align: center; font-size: 0.8rem; line-height: 1.1rem; }
    </style>
    <div class="progress" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
        <div class="bar"></div>
        <div class="label">0%</div>
    </div>
</template>
<script>
function createProgressBar() {
    var progressBar = document.createElement('div');
    var shadowRoot = progressBar.attachShadow({mode: 'closed'});
    shadowRoot.appendChild(document.getElementById('progress-bar-template').content.cloneNode(true));
    progressBar.updateProgress = function (newPercentage) {
        shadowRoot.querySelector('.progress').setAttribute('aria-valuenow', newPercentage);
        shadowRoot.querySelector('.label').textContent = newPercentage + '%';
        shadowRoot.querySelector('.bar').style.width = newPercentage + '%';
    }
    return progressBar;
}
</script>

我们创建一个空的div用于包含我们想要的控件。然后progressBar.attachShadow({mode: 'closed'});创建了一个与div同级的ShadowRoot。然后再利用shadowRoot.appendChild(...)添加想要的控件模板。可以看到内外部样式相互之间并不污染。另外,为了利于调试,我们可以在创建Shadow DOM时配置{mode: DEBUG ? 'open' : 'closed'},这样,就可以通过progressBar.shadowRoot属性获取到ShadowRoot。

控件插槽

现在,你可能非常疑惑为什么要去建立Shadow DOM而不是直接操作CSS?事实上,在第一版的CSS作用域模型中已经对css的作用域问题@scope做出了相关支持。那么我们为什么要另起炉灶又引入一个新的机制来实现样式的作用域隔离呢?其中一个动机是我们需要将Web控件的实现内容隐藏起来,不让一个遍历API诸如querySelectorAllgetElementByTagName操作它。因为在Shadow Dom内部的节点是不能通过通用API操作到的,用户不担心自己使用的控件被自己破坏了。注意到Shadow DOM不会像iframe那样使用跨源安全机制,我们可以轻易的通过特定的API操作Shadow DOM内部的内容。另一个原因是我们需要一个基于DOM的组件解决方案,来看下面的例子:

<ul id="contacts">
    <li>
        Commit Queue
        (<a href="mailto:commit-queue@webkit.org">commit-queue@webkit.org</a>)<br>
        One Infinite Loop, Cupertino, CA 95014
    </li>
    <li>
        Niwa, Ryosuke
        (<a href="mailto:rniwa@webkit.org">rniwa@webkit.org</a>)<br>
        Two Infinite Loop, Cupertino, CA 95014
    </li>
</ul>

我们需要为联系人列表加上一些样式(译者:一般方法非常好实现。但是,注意联系人列表是控件,控件有自己的样式,用特定的class或id标注控件不是本文的目的。),像下面这样:



我们不用在js中往Shadow DOM中插入每个联系人的信息,我可以使用插槽(solt)。首先定义一个这样的模板:

<template id="contact-template">
    <style>
        :host { border: solid 1px #ccc; border-radius: 0.5rem; padding: 0.5rem; margin: 0.5rem; }
        b { display: inline-block; width: 5rem; }
    </style>
    <b>Name</b>: <slot name="fullName"><slot name="firstName"></slot> <slot name="lastName"></slot></slot><br>
    <b>Email</b>: <slot name="email">Unknown</slot><br>
    <b>Address</b>: <slot name="address">Unknown</slot>
</template>
<script>
window.addEventListener('DOMContentLoaded', function () {
    var contacts = document.getElementById('contacts').children;
    var template = document.getElementById('contact-template').content;
    for (var i = 0; i < contacts.length; i++)
        contacts[i].attachShadow({mode: 'closed'}).appendChild(template.cloneNode(true));
});
</script>

模板中运用了slot标签,外部dom中对应内容会自动插入到插槽中。我们需要改写一下联系人列表:

<ul id="contacts">
    <li>
        <span slot="fullName">Commit Queue</span>
        (<a slot="email" href="mailto:commit-queue@webkit.org">commit-queue@webkit.org</a>)<br>
        <span slot="address">One Infinite Loop, Cupertino, CA 95014</span>
    </li>
</ul>

js代码中,我们为每一个<li>创建了一个Shadow DOM,Shadow DOM的模板中定义了一些插槽如<slot name="fullName">,外部li会将对应信息插入模板的插槽中,最终生成的模板就像这样:

<ul id="contacts">
    <li>
        <!--shadow-root-start-->
            <b>Name</b>:
            <slot name="fullName">
                <!--slot-content-start-->
                    <span slot="fullName">Commit Queue</span>
                <!--slot-content-end-->
            </slot><br>
            <b>Email</b>:
            <slot name="email">
                <!--slot-content-start-->
                    <a slot="email" href="mailto:commit-queue@webkit.org">commit-queue@webkit.org</a>
                <!--slot-content-end-->
            </slot><br>
            <b>Address</b>:
            <slot name="address">
                <!--slot-content-start-->
                    <span slot="address">One Infinite Loop, Cupertino, CA 95014</span>
                <!--slot-content-end-->
            </slot>
        <!--shadow-root-end-->
    </li>
</ul>

然后该模板拥有自己的样式。最终效果如下:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,757评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,478评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,540评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,593评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,903评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,329评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,659评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,383评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,055评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,337评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,864评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,227评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,820评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,999评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,750评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,365评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,260评论 2 258

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,625评论 1 92
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,543评论 25 707
  • 姐姐要去援疆了,心里舍不得。毕竟要远离,不能经常见面了。人还没走,心里已经是空落落的……只能躲在一隅,默默...
    蒹葭essay阅读 103评论 0 0
  • 故事前言 作者还小,所以很多不好的地方多多包涵谢谢!文章主要讲女主进入娱乐圈后的各种磨难,至于主角名字不要太在意,...
    雨下的雪i阅读 209评论 0 1
  • 自新入职来,除了上周学习适应阶段,真正开始进入工作状态的就是制作这次标书了。上周看同事做的时候还没有很强烈的参与感...
    潇湘淋毓阅读 210评论 0 1