本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《 阿里云开发者社区用户服务协议 》和 《 阿里云开发者社区知识产权保护指引 》。如果您发现本社区中有涉嫌抄袭的内容,填写 侵权投诉表单 进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

$(document).ready(function(){

$.get("a.html",function(data){ // 初始將 a.html include div#iframe

$("#iframe").html(data);

二 Shadow DOM

2.1 还记得 iframe 们吗?

我们还在使用它们,是因为他们能确保组件和控件的 JavaScript CSS 不会影响页面。 Shadow DOM 也能提供这样的保护,并且没有 iframe 带来的负担。正式的说法是:

Shadow DOM 的设计是在 shadow 根下隐藏 DOM 子树从而提供封装机制。它提供了建立和保障 DOM 树之间的功能界限,以及给这些树提供交互的功能,从而在 DOM 树上提供了更好的功能封装。

如果你做过网站,那么很可能你已经用过一些 JavaScript 类库。既然如此,你可能会对这些不知名的类库作者心存感激。

这些作者—— web 开发领域的勇士们——都面对着同样的一个问题——封装。他们会花大量的精力在面向对象的经典问题之一上面,即如何封装自己的代码,以便与类库使用者的代码分离。

除了 SVG ,现在的 Web 平台只提供了一种原生的方法去隔离代码块,这并不优雅。没错,我说的就是 iframe 。对大部分需要封装的场景来说, frames 太重而且限制太多。

如果我需要把每个自定义的按钮都放到 iframe 里,你是什么感觉,会不会疯掉?

所以,我们需要一些更好的东西。事实上,大部分的浏览器已经变相地提供了一种强大技术去隐藏一些实现细节。这个技术就是所谓的“ shadow DOM ”。

2.2 我的名字是 DOM Shadow DOM

Shadow DOM 是指浏览器的一种能力,它允许在文档( document )渲染时插入一棵 DOM 元素子树,但是这棵子树不在主 DOM 树中。看一个简单的 slider

<input id="foo" type="range"/>

把这段代码放到 webkit 内核的浏览器中,它会这样显示:

很简单吧,这里有一个滑槽,还有一个滑块可以沿滑槽滑动。

嗯。一切看起来都那么美好,喝杯咖啡先……等下等下,这里居然有一个可以在 input 元素中滑动的元素!为什么我不能通过 JavaScript 看到它?

var slider = document.getElementsById("foo");

console.log(slider.firstChild); // 返回 null

2.2 这是一种魔法么?

我的观点来看,不是。这只是 shadow DOM 在起作用。你看,浏览器的开发者们已经意识到了手工编写这些 DOM 元素的表现和行为很困难而且很 SB 。所以,从一定程度上讲,他们骗了我们,给了我们一个输入框,但拥有比输入框更多的功能。

他们为你—— web 开发者设定了一个边界,界定了哪些是你可以访问的,哪些实现细节是访问不到的。然而,浏览器本身却可以随意跨越这个边界。设置这 样一个边界之后,它们就可以在你看不见的地方使用熟悉的 web 技术、同样的 HTML 元素去创建更多的功能,而不是像你一样要在页面上用 div span 来 堆。

有一些很简单,就像上面说的 slider 。而有一些却相当复杂。我们来看一下 video 元素,它有一些按钮、进度条、 hover 态的音量控制,像这样:

所有的这一切都只是 HTML CSS ——但是是隐藏在 shadow DOM 子树中的。

借用 XXX 的一首诗,“它是怎样工作的?”为了直观一些,我们假装可以用 JavaScript 操作它。看这个简单的页面:

<style> p { color: Green; } </style>

</head>

<p>My Future is so bright</p>

<div id="foo"></div>

<script>

var foo = document.getElementById('foo');

// 注意:这里只是模拟,不是真实的 API

foo.shadow = document.createElement('p');

foo.shadow.textContent = 'I gotta wear shades';

</script>

</body>

</html>

我们获得了一个这样的 DOM 树:

<p>My Future is so bright</p>

<div id="foo"></div>

但是它像是被这样渲染出来的:

<p>My Future is so bright</p>

<div id="foo"> <!-- shadow subtree begins -->

<p>I gotta wear shades</p>

</div> <!-- shadow subtree ends -->

看起来是这样:

注意一下,为什么渲染的句子的第二部分不是绿色的?这是因为文档( document )中选择器 p 不能获取到 shadown DOM 。很酷对不对?!如果一个框架开发者被赋予这样的能力会怎么样?想象一下你只需要写你的 widget ,而不用担心被不知哪里蹦出来的选择器愚弄…… 简直令人陶醉。

2.3 事件的情况

为了保持自然, shadow DOM 子树中的事件可以在文档( document )中被监听。比如,你点击一下 audio 元素中的静音按钮,你可以在一个包裹它的 div 中监听到这个事件。

<div onclick="alert('who dat?')">

<audio controls src="test.wav"></audio>

但是,如果你要确认事件的来源,会发现它是 audio 元素,而不是它内部的按钮。

<div onclick="alert('fired by:' + event.target)">

<audio controls src="test.wav"></audio>

为什么这样?因为当事件穿过 shadown DOM 边界的时候,会被重新设定 target ,以避免暴露 shadow DOM 子树内部结构。用这种方式,你可以监听到从 shadow DOM 中产生的事件,而实现者也可以继续隐藏细节。

通过 CSS 访问( Reaching into Shadow

另一个需要提到的技巧是怎样通过 CSS 来访问 shadow DOM 子树。假设我想自定义我的 slider 。我想让它有一些样式,而不是系统原生的那样,像这样:

input[type=range].custom {

-webkit-appearance: none;

background-color: Red;

width: 200px;

结果如下:

很好,但是我怎样定义滑块的样式呢?我们已经知道,常规的 CSS 选择器并不能获取到 shadow DOM 子树。但事实上,这里有一些很方便的伪元素,可以取到 shadow DOM 子树中的元素。例如, slider 中的滑块在 webkit 中可以这样访问:

input[type=range].custom::-webkit-slider-thumb {

-webkit-appearance: none;

background-color: Green;

opacity: 0.5;

width: 10px;

height: 40px;

样子如下:

很完美对不对?想想看,你可以为 shadow DOM 子树中的元素赋予样式,而不需要真的访问到这些元素。而这些 shadow DOM 的作者有了决定哪些部分可以被赋予样式的权利。如果你是作者,在做一些 UI widget toolkit 的时候,难道不想有这样的能力吗?

带有洞( hole )的 Shadow DOM ,无穷的想象力

讲完了这些令人惊叹的能力,我们想象一样,如果给一个有 shadown DOM 子树的元素插入子元素会怎样?我们来实验一下:

// Create an element with a shadow DOM subtree.

var input = document.body.appendChild(document.createElement('input'));

// Add a child to it.

var test = input.appendChild(document.createElement('p'));

// .. with some text.

test.textContent = 'Team Edward';

结果如下:

哇!欢迎来到 twilight DOM 的世界!它是文档( document )的一部分,可以被遍历到,但是不会渲染!它是不是很有用呢?不一定,但是如果你需要的话它确实就在那等你。

但是,如果我们真的有能力把元素的子元素放入 shadow DOM 子树中会怎么样?想象一下 shadow DOM 是一个模板,通过它的某个洞( hole )可以看到内部的子元素:

// 注意:这里只是模拟,不是真实的 API

var element = document.getElementById('element');

// 创建 shadow DOM 子树

element.shadow = document.createElement('div');

element.shadow.innerHTML = '<h1>Think of the Children</h1>' +

'<div class="children">{{children-go-here}}</div>';

// Now add some children.

var test = element.appendChild(document.createElement('p'));

test.textContent = 'I see the light!';

如果你去遍历 DOM ,你会看到这个:

<div id="element">

<p>I see the light</p>

但是像是这样渲染出来的:

<div id="element">

<div> <!-- shadow tree begins -->

<h1>Think of the Children</h1>

<div class="children"> <!-- shadow tree hole begins -->

<p>I see the light</p>

</div> <!-- shadow tree hole ends -->

</div> <!-- shadow tree ends -->

当你添加子元素的时候,从 DOM 树中看像一个正常的子元素,但是渲染的时候,他们从“洞( hole )”中进到了 shadow DOM 子树。

写到这里,你应该会承认,这真的很酷,也会问:

浏览器中什么时候才会有呢?

2.4 家庭作业

你认为听完了这么多说教的内容会没有家庭作业?作为一个 JavaScript 类库或者框架的开发者,尝试者去想象一下你可以利用 shadow DOM 制作的跟之前不一样的伟大的东西。然后想一下 shadow DOM 可以应用到的一些特定的使用场景(加上真实的或者模拟的代码)。

最后,共享你想到的使用场景到 public-webapps 邮件列表。关于在 web 平台中加入这种能力的讨论正在进行,我们需要你的帮助。

如果你不是一个框架作者,你仍然可以参与进来,你可以给 shadown DOM 加油,也可以将这份快乐传播到你最喜欢的社交网络上,因为快乐就是我们工作的全部。

附: SVG shadow DOM

差点忘了,至于你信不信,我反正信了, SVG 确实已经用到了 shadow DOM ,从一开始就是这样。但是比较麻烦的是, SVG shadow DOM 非常……非常……水( shady ),不不,不是这个词,是另一个词,以 sh 开头,以 y 结尾。(注:对英文语境不是太熟悉,评论中有人提到是 shy 。)对对,就是它!我可以继续说,但是请相信我对 SVG shadow DOM 的评价。或者你可以查看文档。

原文地址: http://www.toobug.net/article/what_is_shadow_dom.html

三 其他实践参考

3.1 shadow dom UI web components 思想如何应用于实际项目

http://www.cnblogs.com/yexiaochai/p/4167554.html

3.2  Shadow DOM :基础

http://www.ituring.com.cn/article/177461

四 实现范例