Shadow DOM系列2-基础

英文链接:Shadow DOM: The Basics, 27 AUGUST 2013 on Web Components, Shadow DOM

在我的上一篇博文里,我介绍了 Shadow DOM 并举例说明为啥它这么重要。今天,我们要干点苦力活——“码”上见分晓!在本文的最后,你将能够创建自己的封装组件——它封装了外部的内容,并重新整理内部结构来产生一个完全不同的东西。

让我们开始吧!

环境支持

为了能尝试下面的实例,建议使用 Chrome 33 或者更高版本的 Chrome。

一个简单的例子

让我们看一个非常简单的 HTML 文档。

<body>  
  <div class="my-widget">
    <h1>我的组件的标题</h1>
    <p>一些组件的内容</p>
  </div>
  <div class="my-other-widget">
    <h1>我的另一个组件的标题</h1>
    <p>一些另一个组件的内容</p>
  </div>
</body>

当 HTML 转化成 DOM,每个元素都会变成一个节点(node)。而一组互相嵌套的一组节点则被称为节点树(node tree)

Shadow DOM 的独特之处在于它允许我们创建自己的节点树,这种节点树被称为影子树(shadow trees)。影子树对其中的内容进行了封装,有选择性的进行渲染。这就意味着我们可以插入文本、重新安排内容、添加样式等等。举个栗子:

<div class="widget">Hello, world!</div>  
<script>  
    var host = document.querySelector(‘.widget‘);
    var root = host.createShadowRoot();
    root.textContent = ‘我在你的 div 里!‘;
</script>

通过上面的代码,我们已经通过一个影子树替换掉了我们 .widget div 的文本内容。要创建一个影子树,我们首先要指定一个节点担任影子宿主(shadow host)。在这个例子里,我们将 .widget当做我们的影子宿主。然后我们给影子宿主添加一个称作影子根(shadow root)的新节点。影子根作为影子树的第一个节点,其他的节点都是它的子节点。

如果你使用 Chrome 开发者工具检查这个元素,你会看到下面这样的结构:

看到 #shadow-root 是怎么被标灰显示的吗?这就是我们刚才创建的影子根。结果表明,影子宿主里面的内容没有被渲染,反而影子根里面的内容被渲染了出来。

这就是当实例运行时,我们看到 Im inside yr div! 而不是 Hello, world! 的原因。

让我们在一张图里可视化地查看一下整个过程:

为了更深入的了解 Shadow DOM,我们再举一个栗子:

又一个简单的例子

<body>
  <div class="widget">Hello, world!</div>
  <script>
    var host = document.querySelector(‘.widget‘);
    var root = host.createShadowRoot();

    var header = document.createElement(‘h1‘);
    header.textContent = ‘一只野生的影子标题出现了!‘;

    var paragraph = document.createElement(‘p‘);
    paragraph.textContent = ‘一些影子文本也探头探脑滴冒了出来…‘;

    root.appendChild(header);
    root.appendChild(paragraph);
  </script>
</body>

我沿用了之前的例子并给它添加了两个新元素,之所以这样做是为了说明 Shadow DOM 的操作与普通的DOM 的操作区别真的不大。你仍然可以使用 appendChild 和 insertBefore 来将子节点添加到父节点上,在 Chrome 的开发者工具里可以查看到这样的代码:

和之前一样,影子宿主 里面的内容 Hello world 并没有被渲染,取而代之展现的的是 影子根

你可能会说:“这个我知道,但我如果想要渲染影子宿主里的内容,那该怎么玩?”

既然你诚心诚意的发问了,我就大发慈悲的告诉你——这绝对是 Shadow DOM 的一个杀手锏!请耐心读下去,我会向你展示它的神奇之处。

content 标签

在之前的两个例子里,我们用影子根里面的内容完全替换掉了影子宿主里面的内容。但这种奇技淫巧在实际开发中没什么用。真正有用的是我们可以从影子宿主中获取内容,并使用影子根中的结构将这些内容呈现。像这种将内容与实现分离的方式让我们可以更加灵活的处理页面的呈现。

想要引用影子宿主里面的内容,我们首先需要采用一个新的标签—— <content> 标签。这儿又是一个栗子:

<body>
  <div class="pokemon">
    胖丁  
  </div>

  <template class="pokemon-template">
    <h1>一只野生的 <content></content> 出现了!</h1>
  </template>

  <script>
    var host = document.querySelector(‘.pokemon‘);
    var root = host.createShadowRoot();
    var template = document.querySelector(‘.pokemon-template‘);
    
    root.appendChild(document.importNode(template.content, true));
  </script>
</body>

译者注:pokemon 就是口袋妖怪的意思。

使用 <content> 标签,我们创建了一个插入点(insertion point),其将 .pokemon div 中的文本投射(projects) 出来,使之得以在我们的影子节点 <h1> 中展示。插入点十分强大,它允许我们在不改变源代码的情况下改变渲染顺序,这也意味着我们可以对要呈现的内容进行选择。

你可能已经注意到我们已经使用了一个模板标签 <template> 而不是整个用 JavaScript 来构建 shadow DOM。我发现使用 <template> 标签令 shadow DOM 的使用过程更加简单。

让我们看一个使用进阶的例子来证明如何使用多个插入点。

select 属性

<body>
  <div class="bio">
    <span class="first-name">劳勃</span>
    <span class="last-name">多德森</span>
    <span class="city">旧金山</span>
    <span class="state">加利福尼亚州</span>
    <p>我专注前端开发(HTML/CSS/JavaScript),还会一点 Node 和 Ruby。</p>
    <p>从事写作,偶尔也会写点博客。</p>
    <p>尽管我是南方人,但我最近都在美丽的旧金山工作和生活。</p>
  </div>

  <template class="bio-template">
    <dl>
      <dt>名字</dt>
      <dd><content select=".first-name"></content></dd>
      <dt>姓氏</dt>
      <dd><content select=".last-name"></content></dd>
      <dt>城市</dt>
      <dd><content select=".city"></content></dd>
      <dt>州</dt>
      <dd><content select=".state"></content></dd>
    </dl>
    <p><content select=""></content></p>
  </template>

  <script>
    var host = document.querySelector(‘.bio‘);
    var root = host.createShadowRoot();
    var template = document.querySelector(‘.bio-template‘);
    
    root.appendChild(template.content);
  </script>
</body>

在这个例子中我们创建了一个非常简单的简历组件。因为每个定义的字段都需要特定的内容,我们必须告诉 <content> 标签有选择性的插入内容。为了做到这一点,我们使用 select 属性。select属性使用 CSS 选择器来选取想要展示的内容。

举例来说,<content select=".last-name"> 会在影子宿主里寻找任何样式名称为 .last-name 的元素。如果找到一个匹配的元素,其就会将这个元素渲染到 shadow DOM 中对应的 <content> 标签中去。

改变顺序

通过插入点,我们不必修改 content 内容的结构而改变渲染的顺序。请记住,内容存在于影子宿主中,而呈现的方式存在于影子根也就是 shadow DOM 中。这有一个不错的示例,它展示了如何将姓氏一栏和名字一栏的渲染顺序进行交换。

<template class="bio-template">  
  <dl>
    <dt>姓氏</dt>
    <dd><content select=".last-name"></content></dd>
    <dt>名字</dt>
    <dd><content select=".first-name"></content></dd>
    <dt>城市</dt>
    <dd><content select=".city"></content></dd>
    <dt>州</dt>
    <dd><content select=".state"></content></dd>
  </dl>
  
  <p><content select=""></content></p>
</template>

通过对我们的 template 模板进行简单的修改,我们就在不更改影子宿主内容的前提下对展示的效果进行了替换。为了更好的理解上面的内容,请看一下 Chrome 开发者工具的检查元素:

如图所示,.first-name 节点依然是影子宿主的第一个子节点,但是我们让它显示在 .last-name 节点之后了。我们通过改变插入点的顺序来完成了这一切,仔细回味一下你就会发现这个功能的强大之处。

贪心插入点(Greedy Insertion Points)

你可能已经注意到了,在 .bio-template 模板的最后,我们有一个 content 标签,他的 select 属性值为空。

<p><content select=""></content></p>

这种被称作通配符选择器(wildcard selection),其可以抓取影子宿主中所剩余的全部内容。以下三种选择器是完全相等的:

<content></content>  <conent select=""></conent>  <content select="*"></content>

我们来实验一把,将通配符选择器移到 template 模板的顶部:

<template class="bio-template">  
  <p><content select=""></content></p>
  <dl>
    <dt>Last Name</dt>
    <dd><content select=".last-name"></content></dd>
    <dt>First Name</dt>
    <dd><content select=".first-name"></content></dd>
    <dt>City</dt>
    <dd><content select=".city"></content></dd>
    <dt>State</dt>
    <dd><content select=".state"></content></dd>
  </dl>
</template>

你会注意到所有的内容都被挪到 <p> 标签中去了,这完全的改变了我们组件的展示。这是因为这个选择器是贪心的,而且元素只能被选择一次。我们一旦把贪心选择器放在了模板的顶部,他就会将所有内容都抓取,不给其他 select 选择器留一点内容。

Dominic Cooney(@connsta)在他的博文 Shadow DOM 101 中对贪心选择器有很好的描述。文中他将选择器的原理比作舞会的邀请函:

<content> 元素是一封将文档(document)内容邀请去 Shadow DOM 渲染舞会的请柬。这些邀请按序发出;谁能收到请柬取决于请柬发往的地址(也就是 select 属性)。对于内容元素来说,一旦收到请柬就会欣然接受并立即动身——谁会不接受这样一份盛大舞会请柬呢?如果接下来又有一份请柬发送到这一地址,额,不好意思,现在家里已经没人能去了。

掌握选择器和插入点的用法也是挺麻烦的,所以 Eric Bidelman(@ebidel)写了一个插入点可视化工具来帮助阐明这一概念。

他还录了一个视频以便解释这一概念:

YouTube 视频地址(需翻墙)

结论

我还想多唠点,不过今天还是先收个尾吧。明天我们将深入讨论 CSS 样式封装,再往后我们还会讨论 JavaScript 和用户交互的内容。和往常一样,有问题的话可以到我的 twitter上艾特我或者给我留言~感谢阅读~

时间: 2024-10-12 08:11:14

Shadow DOM系列2-基础的相关文章

Shadow DOM系列6-综述

Web Components 系列主要由自定义元素(Custom Elements).HTML 引入(HTML Imports)和影子 DOM(shadow DOM) 组成,而 Shadow DOM 无疑是当中的重中之重.本文对下面翻译的几篇文章进行综述,总结了 Shadow DOM 术语梳理和结构关系. 术语标注 影子 DOM(shadow DOM):是一种依附于文档原有节点的子 DOM,具有封装性 光明 DOM(light DOM):就是原生的 DOM,为了与影子 DOM 区别采用的名词 影

DOM系列---基础篇

DOM (Document Object Model) 即文档对象模型, 针对 HTML 和 XML 文档的 API (应用程序接口) .DOM 描绘了一个层次化的节点树,运行开发人员添加.移除和修改页面的某一部分.DOM 产生于 网景公司及微软公司创始的 DHTML(动态 HTML) ,但现在它已经成为表现和操作页面标记的真正跨平台.语言中立的方式. DOM 中的三个字母: D(文档)可以理解为整个 Web 加载的网页文档: O(对象)可以理解为类似 window 对象之类的东西,可以调用属性

DOM系列---基础篇[转]

DOM (Document Object Model) 即文档对象模型, 针对 HTML 和 XML 文档的 API (应用程序接口) .DOM 描绘了一个层次化的节点树,运行开发人员添加.移除和修改页面的某一部分.DOM 产生于 网景公司及微软公司创始的 DHTML(动态 HTML) ,但现在它已经成为表现和操作页面标记的真正跨平台.语言中立的方式. DOM 中的三个字母: D(文档)可以理解为整个 Web 加载的网页文档: O(对象)可以理解为类似 window 对象之类的东西,可以调用属性

Shadow DOM 与 HTML Templates

在之前的Web Components系列文章中,简单介绍了Web Components概要,HTML Templates,Shadow DOM,Shadow DOM(二). 本文将在此基础上,介绍Shadow DOM与HTML Templates如何一起协作. 在Shadow DOM(二)中,介绍了如何创建Shadow DOM:通过调用createShadowRoot()方法创建root节点,然后在JavaScript中创建普通的HTML节点, 并将这些节点附到Shadow Root上.但这种方

Shadow DOM获取Shadow host的内容

在Shadow DOM(二)中,介绍了与Shadow DOM相关的概念,包括Shadow host等等. 本文将重点介绍如何将Light DOM中的内容传到Shadow DOM中. 而在Shadow DOM 与HTML Templates一文的示例中可以看到Shadow host: <div class="host">Hello World!</div>的内容在该节点创建并附加Shadow Root后并没有在浏览器中得到渲染,也就是说Shadow host的内容

DOM系列---DOM操作样式

发文不易,若转载传播,请亲注明出处,谢谢! 一.操作样式 CSS作为(X)HTML的辅助,可以增强页面的显示效果.但不是每个浏览器都能支持最新的CSS能力.CSS的能力和DOM级别密切相关,所以我们有必要检测当前浏览器支持CSS能力的级别. DOM1级实现了最基本的文档处理,DOM2和DOM3在这个基础上增加了更多的交互能力,这里我们主要探讨CSS,DOM2增加了CSS编程访问方式和改变CSS样式信息. DOM一致性检测 功能 版本号 说明 Core 1.0.2.0.3.0 基本的DOM,用于表

快速入门系列--WebAPI--01基础

ASP.NET MVC和WebAPI已经是.NET Web部分的主流,刚开始时两个公用同一个管道,之后为了更加的轻量化(WebAPI是对WCF Restful的轻量化),WebAPI使用了新的管道,因此两者相关类的命名空间有细微差异,在使用时需要注意. WebAPI学习系列目录如下,欢迎您的阅读! 快速入门系列--WebAPI--01基础 快速入门系列--WebAPI--02进阶 快速入门系列--WebAPI--03框架你值得拥有 快速入门系列--WebAPI--04在老版本MVC4下的调整 W

Windows Azure系列 -《基础篇》- 创建虚拟网络

如何在Windows Azure中创建虚拟网络,以构建云环境中的虚拟局域网: 1.登陆Windows Azure平台,点击侧边栏网络按钮,在中间点击"创建虚拟网络". 2.在接下来的配置页面,填写虚拟网络的名称.选择地理外置和地缘组(如果已有),没有则选择创建新的地缘组. 3.接下来填写DNS服务器地址(没有则留空) 4.规划和配置IP网络,选择适用的地址空间. 5.最后点击确认按钮完成. Windows Azure系列 -<基础篇>- 创建虚拟网络,布布扣,bubuko.

Windows Azure系列 -《基础篇》- 如何创建虚拟机

首先,使用自己的windows azure账号登陆管理平台manage.windowsazure.cn,找到并点击"虚拟机"标签,即可看到目前云平台中你所拥有的虚机实例,在我的环境中现在没有任何的虚机,所以我们可以通过点击图示的新建按钮进行选择或直接点击"创建虚拟机"进行创建: 点击"创建虚拟机",在DNS名称位置填写所建虚机的主机名,并选择映像和虚机大小(可选单核至8核,内存从768M至56G),这里说明一下,处于用户名不可以设置为常用的adm