现代 CSS

Web Components Demo: Templates 和 Shadow DOM

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

最好在Chrome 36+测试教程中的示例代码。同时打开开发者工具,将Settings > General > Elements中的Show user agent shadow DOM选项选中。

Web Components

最近将大部分时间花在了Web Components上面,不过这些花费的时间是有价值的。我整理了一个小组件,能更好的帮助大家更好的理解一个整体的Web Components。

DEMO下载源码

Web Components主要由四个部分组成(模板、自定义元素、Shadow DOM和导入),但在这个案例中只关注其中的两个部分:模板(<template>和Shadow DOM)。其中更主要的是模板。在写这篇文章的时候,浏览器对Web Components的支持度还不是很广,为了让浏览器能正常的渲染,需要使用PolymerX-Tag的polyfill库。我想在内部工作中使用之前先仔细研究一下polyfill。

Web Components的简介

Web Components是一种新兴技术,用来规范组件的定制,这些都要非常感谢W3C组织。Web Components的目的就是允许开发人员使用HTML、CSS和JavaScript来自定义元素。这些元素可以被认为是一些小部件(widgets)。

一个很好的示例就是自定义元素<github-card>。如果你有一个GitHub账号,你可以打开<github-card>示例页面,在输入框中输入你的GitHub的用户名,可以看到你的GitHub相关信息。然后你可以到<github-card>文档下载相应的源码,查看如何使用这样的一个标签元素。

Web Components

Web Components主要组成:

  • 模板(<template>标签): 定义的标记块,不会被渲染但可以随后被激活使用。阅读更多的细节...
  • Shadow DOM: 封装的DOM子树,更可靠的用户界面元素组成。最好是把它想成DOM中的DOM。 阅读更多的细节...
  • Custom Elements (自定义元素):让用户自定义新的标签名和新的脚本接口。例如<github-card>阅读更多的细节...
  • HTML Imports(导入): 可以通过<link>标签,把一小块的HTML代码加载到页面中。阅读更多的细节...

W3C规范文档中,Web Components除了上述的四个部分还有一个Decorators,基于CSS选择器来应用模板,从而对文档进行丰富的视觉和行为的变化。更多的详细信息可以点击这里阅读。但很多开发人员不喜欢它,所以没有很多人去敲定其规范,有可能将来会消失。

比如<github-card>具有Web Components所有功能部分,每个都可能很好的使用。但当它们一起工作的时候,就组成了一个Web Components。从概念上讲,它有点类似于AJAX,组合在一起执行一个任务。

专注模板(一切从模板开始)

我读过Web Components中所有部分(包括"decorator")的介绍和写过一点代码,但给我的感觉是,学习Web Components最好的方法不是阅读而是动手去写。所以,针对Web Components这几个组成部分,我们从模板开始着手。

对于模板,我想展示一个基于JavaScript数据对象创建的简单的书籍列表。事情就是这样开始的...

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>JavaScript Books</title>
  <link rel="stylesheet" href="css/normalize.min.css">
  <link rel="stylesheet" href="css/bootstrap.min.css">
  <link rel="stylesheet" href="css/styles.css">
</head>
<body>
  <div id="container">
    <header>
      <h1 class="page-header">JavaScript Books</h1>
      <h2>Built with templates & Shadow DOM</h1>
    </header>
    <template id="singleBook">
      <style>
        .templateArticle {
          display: inline-block;
          margin: 6px;
        }
        .btn {
          margin: 10px;
          float: right;
        }
        .thumbnail {
          margin-bottom: 0;
        }
        .bookTitleClass {
          text-align: left;
        }
        #bookTitle {
          font-style: italic;
        }
      </style>
      <article class="templateArticle panel panel-default">
        <header class="panel-heading">
          <h2 class="panel-title bookTitleClass">
            <span id="bookTitle"></span>
            <br />
            by <span id="bookAuthor"></span>
          </h2>
        </header>
        <img src="" alt="" class="thumbnail">
        <a href="" id="btnPurchase" class="btn btn-primary" role="button" target="blank">Buy at Amazon</a>
      </article>
    </template>
    <section id="allBooks" class="allBooksClass"></section>
    <script src="scripts/main.js"></script>
  </div>
</body>
</html>

CSS (css/style.css)

body {
  margin: 20px;
}
h1, h2 {
  text-align: center;
}
footer {
  text-align: center;
  margin-top: 20px;
}
.allBooksClass {
  margin-top: 30px;
  text-align: center;
}

JavaScript(js/main.js)

(function(){

  var jsBooks = {
    "book1" : {
      "title": "Object-Oriented Javascript",
      "author": "Stoyan Stefanov",
      "image": "images/ooj.jpg",
      "amazonLink": "http://amzn.to/1sRFbEC"
    },
    "book2" : {
      "title": "Effective Javascript",
      "author": "David Herman",
      "image": "images/effectivejs.jpg",
      "amazonLink": "http://amzn.to/1pLu1A5"
    },
    "book3" : {
      "title": "JavaScript: The Good Parts",
      "author": "Douglas Crockford",
      "image": "images/goodparts.jpg",
      "amazonLink": "http://amzn.to/1ukjoIN"
    },
    "book4" : {
      "title": "Eloquent Javascript",
      "author": "Marijn Haverbeke",
      "image": "images/eloquentjavascript.jpg",
      "amazonLink": "http://amzn.to/1lPP6pn"
    }
  };

  var template = document.querySelector("#singleBook"),
    templateContent = template.content,
    host = document.querySelector("#allBooks"),
    root = host.createShadowRoot();

  for (key in jsBooks) {
    var title = jsBooks[key].title,
      author = jsBooks[key].author,
      image = jsBooks[key].image,
      amazonLink = jsBooks[key].amazonLink;

    templateContent.querySelector("img").src = image;
    templateContent.querySelector("img").alt 
    = templateContent.querySelector("#bookTitle").innerHTML
    = title;
    templateContent.querySelector("#bookAuthor").innerHTML = author;
    templateContent.querySelector("#btnPurchase").href = amazonLink;
    root.appendChild(document.importNode(templateContent, true));
  }
})();

index.html引入了normalize.cssTwitter Bootstrap的样式文件bootstrap.css。Bootstrap提供了响应式布局功能,这里引入主要是为了让页面布局看上去好看一些。另外引入style.css文件,这个文件主要是对页面一些元素的样式做了定义,在整页案例中他是一个小角色。

HTML和过去一样,不同的是给Web Components中心部分template标签添加了一个IDsingleBook。把HTML代码和CSS样式以<style>放在了<template>里面。

<template>中有一个<article>标签:有关于书的数据将解析到这里面。因为模板是惰性的,这意味着如果不和外面通信,那么页面加载这部分是不可见的。

注意,<article>里面部分是空的:

  • 两个<span>标签
  • <img>标签中的srcalt属性
  • <a>标签中的href属性

这些空的部分将是用来填充我们的对象数据。接下来我们一起来看看...

(function(){
...
})();

所有东西都包裹在一个IIFE

var jsBooks = {
  "book1" : {
    "title": "Object-Oriented Javascript",
    "author": "Stoyan Stefanov",
    "image": "images/ooj.jpg",
    "amazonLink": "http://amzn.to/1sRFbEC"
  },
...
};

JavaScript 数据对象。这里仅列出其中的一个列表,而每个列表都包括了四个项目,每一个项目都是JavaScript书特定的信息。每个列表都包含了titleauthorimageamazonLink属性。

var template = document.querySelector("#singleBook"),
    templateContent = template.content,
    host = document.querySelector("#allBooks"),
    root = host.createShadowRoot();

开始创建一个Shadow DOM。我通过var创建了四个变量。

  • template直接引用了要渲染的<template>,直接引用它的IDsingleBook
  • templateContent定义了模板要渲染的内容取决于页面加载时<template>content属性值。详细阅读,点击这里
  • host直接引用了所谓的shadow root,也就是模板内容将要加载到页面的那个元素。在这个示例中,就是页面中的<section id="allBooks">元素。它通常被称为shadow root,你可以定义成任何你想要的变量名,但一般约定其变量名为host
  • root直接引用了shadow root,将生成的内容插入到template中。host.createShadowRoot()内容插入到root中。在这个示例就中是<section id="allBooks">元素中。它可能更会认为是一个真正的Shadow DOM,内容加载到root时,将会返回Web页面的文档片段(有关于文档片段的内容可以点击这里了解)。其实你也可以将其定义你想定义的变量名,不过默认情况下,大家喜欢将其命名为root

for (key in jsBooks) {
...
};

使用一个for ... in循环,将jsBooks对象内容填充到模板中。代码拆解为:

var title = jsBooks[key].title,
    author = jsBooks[key].author,
    image = jsBooks[key].image,
    amazonLink = jsBooks[key].amazonLink;

jsBooks对象中的列表值指定给对应的变量:

templateContent.querySelector("img").src = image;

循环遍历模板中的<img>标签的src属性,并且将image值赋予给它。

templateContent.querySelector("img").alt 
  = templateContent.querySelector("#bookTitle").innerHTML
  = title;

循环遍历模板中的<img>标签的alt属性,并且将title值赋予给它。

同时遍历模板中的#bookTitle元素(一个<span>标签),并且把title值赋予给它。

templateContent.querySelector("#bookAuthor").innerHTML = author;

循环遍历模板中的#bookAuthor元素(一个<span>标签),并且把author值赋予给它。

templateContent.querySelector("#btnPurchase").href = amazonLink;

循环遍历模板中的#btnPurchase元素(仅有的<a>标签)的href属性,并且将amazonLink值赋予给它。

root.appendChild(document.importNode(templateContent, true));

接下来,我们要花点时间来讨论这行代码。

在代码中,我们所有数据对象填充到模板中,都是由templateContent变量完成。但它返回的是文档片段。

文档片段不是页面DOM的一部分,在这个示例中,将文档片段视为外部的一个文件。通过document.importNode()函数可以将外部文档(所说的文档片段)填充真实的参数,将内容重复的复制(复制一切)。

从那里,我们把root当作父元素,并将文档片段当作其子元素填充到里面。常使用document.importNode()将文档片段填充到root中。

有关于document.importNode()更多的介绍,可以点击这里进行了解

如果我们在一个选中了Show user agent shadow DOM的Chrome 36+浏览器中审查index.html。通过开发都工具的Inspect Element查看示例中的<section>标签(show host),你将看到的模板内容(show host)如下所示:

Web Components

但是有一个问题,Bootstrap样式用于<template>模板中某些元素的样式被忽略了。任何包含panelbtn类名的元素应该会引用Bootstrap的样式,尤其是按钮...

Web Components

这里发生的一切,正如前面所说的模板内的代码不能和模板外的代码做任何的交流。从技术上说<template>在Shadow DOM,它是一个naturally-encapsulated。所以页面中三个样式文件(normalize.min.css, bootstrap.min.cssstyles.css)在模板的布局中都没生效。现在使用<link>将样式添加到Shadow DOM中是不允许的。

导入样式文件

style.css文件与模板布局无关,但其它两个样式文件有关系。解决方案就是通过@import在模板的<style>中将样式文件引入进来。

<style>
  @import url("css/normalize.min.css");
  @import url("css/bootstrap.min.css");
...
</style>

使用@import是如何解决这个问题的呢?正如Google的@Rob Dodson在他的文章《A Guide to Web Components》介绍在样式表中使用Polymer的声明来解决XHR的请求。

注意,通过@import引入的样式文件不能是域名的地址。比如这个示例,如果直接通过@import导入BootStrap官网提供的CDN地址,template的布局样式仍然无效。

另外一个问题,循环克隆模板中的内容,造成样式表越来越多,几乎增加了四倍,但我们实际上只需要一个就够:

Web Components

调整循环

可以通过改变循环的过程:每次循环的时候,只循环定义了类名.templateArticle<article>标签,并将其插入到<section>标签中。而在循环外,将<style>添加到<section>标签中,也就是shadow host

需要改变的JavaScript从这里开始:

(function(){
...
    root.appendChild(document.importNode(templateContent, true));
  }
})();

改变成:

(function(){
...
    root.appendChild(document.importNode(templateContent.querySelector(".templateArticle"), true));
  }
  root.appendChild(document.importNode(templateContent.querySelector("style"), true));
})();

现在在Shadow DOM中只有一个<style>,而且样式都是对的:

Web Components

因为使用appendChild()<style>添加到<section>中,所以<style>放在底部。如查要产生这样的代码,我将会尝试使用类似jQuery.prepend()方法将其移到顶部。

当然在这个项目中,<style>放在底部并不会影响其工作。只不过我在学习模板和Shadow DOM制作,才想这样做,结构更清晰。如果想了解有关于jQuery.prepend()更多方面的知识,可以点击这里

扩展阅读

上面Rob Dodson文章的链接和HTML5 Rocks有一系列关于Web Components的教程。Rob Dodson的那篇文章详细介绍了Web Components,当然你可能会觉得这篇文章有点老。不过接下来我会一篇一篇的阅读HTML5 Rocks 上介绍Web Components的文章

W3C上有一篇老文章叫作Web Components的介绍(中文译文)。这是一年前的发布的一个工作草案,如果你阅读的话要记住,它是一篇老的规范草案,并没有更新。

的确如此,W3C也提到了最近在Wiki上审阅Web Components。并且有很多链接都指向HTML5 Rocks,还有在Github上也提供了Shadow DOM译文),Custom Elements(译文)和HTML Imports(译文)规范说明。WHATWG上也提供了模板规范的正确版本。

规范可以详细阅读,但要找到一个好的方法去阅读。

最主要的是,微软公司已经发布了Web Components的特性得到支持和不支持具体时间。我假设,未来都会支持Web Components的特性。有关于详细的时间表可以浏览modern.ie状态页面。

通过polyfill来提出IE的问题是一个很好的建议。注意,目前Polymer是最受欢迎的Web Components的polyfill,不过其只支持IE10。有关于Polymer浏览器的兼容性可以点击这里阅读

X-Tag虽然没有Polymer那么受欢迎,但它得到较多浏览器的支持,包括IE9。有关于X-Tag更多信息,可以阅读X-Tag的官方文档

总结

使用类似Polymer和X-Tag这样的Web Components库,就可以在工作中使用Web Components。所以在使用之前,最好先阅读他们的底层代码。

我不能说我的代码是完美的,但我完成了我定下的目标,能够解决我的问题,我所面临的问题是编码而不是阅读。我觉得能写出一个比以前更优秀的Web Components,就算是成功达到目标。

DEMO下载源码

本文根据@Kai Gittens的《Web Components Demo: Templates and (some) Shadow DOM》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://kaidez.com/web-components-demo/

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。中国Drupal社区核心成员之一。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:http://www.w3cplus.com/web-components/web-components-demo-template-and-some-shadow-dom.htmlAir Force 1 High

返回顶部