广告招人:阿里巴巴招前端,在这里你可以享受大公司的福利和技术体系,也有小团队的挑战和成长空间。
联系: qingguang.meiqg at alibaba-inc.com
本文是一篇 Web Components 入门教程
What are Web Components
Web Components 是 W3C 定义的新标准,目前还在草案阶段。
Why are they important
-
前端组件化
bootstrap
// 初始化
$('#myModal').modal({
keyboard: false
});
// 显示
$('#myModal').modal('show');
// 关闭事件
$('#myModal').on('hidden.bs.modal', function (e) {
// do something...
});
atom
// 初始化组件
var dialog = new Dialog(
trigger: '#trigger-btn',
title: '我是自定义的标题',
content: 'hello world',
buttons: ['submit', 'cancel']
});
// 显示
dialog.show();
// 关闭事件
dialog.after('hide', function() {
// do something...
});
统一标准、减少轮子
-
简化代码,提高可维护性
![gmail](http://www.html5rocks.com/zh/tutorials/webcomponents/customelements/gmail.png)
<hangout-module>
<hangout-chat from="Paul, Addy">
<hangout-discussion>
<hangout-message from="Paul" profile="profile.png"
datetime="2013-07-17T12:02">
<p>Feelin' this Web Components thing.</p>
<p>Heard of it?</p>
</hangout-message>
</hangout-discussion>
</hangout-chat>
<hangout-chat>...</hangout-chat>
</hangout-module>
关键技术
HTML Imports
HTML Templates
Custom Elements
Shadow DOM
虽然大部分浏览器还不支持 Web Components ,但是有个叫做 webcomponents.js 的兼容库,可以让 Web Components 在不支持它的浏览器上运行起来。只要你在项目中引入这个库,就可以在其他浏览器中将 Web Components 用起来。
HTML Imports
通过<link>
标签来引入 HTML 文件,使得我们可以用不同的物理文件来组织代码。
<link rel="import" href="http://example.com/component.html" >
注意:受浏览器同源策略限制,跨域资源的 import 需要服务器端开启 CORS。
Access-Control-Allow-Origin: example.com
通过import
引入的 HTML 文件是一个包含了 html, css, javascript 的独立 component。
<template>
<style>
.coloured {
color: red;
}
</style>
<p>My favorite colour is: <strong class="coloured">Red</strong></p>
</template>
<script>
(function() {
var element = Object.create(HTMLElement.prototype);
var template = document.currentScript.ownerDocument.querySelector('template').content;
element.createdCallback = function() {
var shadowRoot = this.createShadowRoot();
var clone = document.importNode(template, true);
shadowRoot.appendChild(clone);
};
document.registerElement('favorite-colour', {
prototype: element
});
}());
</script>
HTML Templates
关于 HTML 模板的作用不用多讲,用过 mustache、handlbars 模板引擎就对 HTML 模板再熟悉不过了。但原来的模板要么是放在 script
元素内,要么是放在 textarea
元素内,HTML 模板元素终于给了模板一个名正言顺的名分: <template>
原来的模板形式:
script 元素
<script type="text/template">
<div>
this is your template content.
</div>
</script>
textarea 元素
<textarea style="display:none;">
<div>
this is your template content.
</div>
</textarea>
现在的模板形式:
template 元素
<template>
<div>
this is your template content.
</div>
</template>
主要有四个特性:
惰性:在使用前不会被渲染;
无副作用:在使用前,模板内部的各种脚本不会运行、图像不会加载等;
内容不可见:模板的内容不存在于文档中,使用选择器无法获取;
可被放置于任意位置:即使是 HTML 解析器不允许出现的位置,例如作为
<select>
的子元素。
Custom Elements
自定义元素允许开发者定义新的 HTML 元素类型。带来以下特性:
定义新元素
元素继承
扩展原生 DOM 元素的 API
定义新元素
使用 document.registerElement()
创建一个自定义元素:
var Helloworld = document.registerElement('hello-world', {
prototype: Object.create(HTMLElement.prototype)
});
document.body.appendChild(new Helloworld());
标签名必须包含连字符 ' - '
合法的标签名:
<hello-world>
,<my-hello-world>
不合法的标签名:
<hello_world>
,<HelloWorld>
元素继承
如果 <button>
元素不能满足你的需求,可以继承它创建一个新元素,来扩展 <button>
元素:
var MyButton = document.registerElement('my-button', {
prototype: Object.create(HTMLButtonElement.prototype)
});
扩展原生 API
var MyButtonProto = Object.create(HTMLButtonElement.prototype);
MyButtonProto.sayhello = function() {
alert('hello');
};
var MyButton = document.registerElement('my-button', {
prototype: MyButtonProto
});
var myButton = new MyButton();
document.body.appendChild(myButton);
myButton.sayhello(); // alert: "hello"
实例化
使用 new
操作符:
var myButton = new MyButton();
myButton.innerHTML = 'click me!';
document.body.appendChild(myButton);
或,直接在页面插入元素:
<my-button>click me!</my-button>
生命周期
元素可以定义特殊的方法,来注入其生存周期内的关键时间点。生命周期的回调函数名称和时间点对应关系如下:
createdCallback: 创建元素实例时
attachedCallback: 向文档插入实例时
detachedCallback: 从文档移除实例时
attributeChangedCallback(attrName, oldVal, newVal): 添加,移除,或修改一个属性时
var MyButtonProto = Object.create(HTMLButtonElement.prototype);
MyButtonProto.createdCallback = function() {
this.innerHTML = 'Click Me!';
};
MyButtonProto.attachedCallback = function() {
this.addEventListener('click', function(e) {
alert('hello world');
});
};
var MyButton = document.registerElement('my-button', {
prototype: MyButtonProto
});
var myButton = new MyButton();
document.body.appendChild(myButton);
Shadow DOM
Shadow DOM 是一个 HTML 的新规范,其允许开发者封装自己的 HTML 标签、CSS 样式和 JavaScript 代码。Shadow DOM 使得开发人员可以创建类似 <input type="range">
这样自定义的一级标签。
web 开发经典问题:封装。如何保护组件的样式不被外部 css 样式侵入,如何保护组件的 dom 结构不被页面的其他 javascript 脚本修改。大家都用过 Bootstrap,如果要使用其中的某些组件,例如 modal,通常会把组件的 DOM 结构复制过来。
<div class="modal fade">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">Modal title</h4>
</div>
<div class="modal-body">
<p>One fine body…</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
这样一坨复制过来的代码,大多数时候并没有仔细了解,任何时候一个不小心都有可能覆盖了其中的一个 class 样式,这里面可能潜在很多小 bug。Shadow Dom 可以很好的解决组件封装问题。
一个例子说明,什么是 Shadow DOM ?
浏览器渲染 <input type="range">
标签,显示结果如下:
<input type="range">
看起来似乎很简单,只有一个 input
标签而已。但实际上是这样的:
显示 shadow dom 需要开启 Chrome 开发者工具的 'Show user agent shadow DOM'
创建 Shadow DOM
使用 createShadowRoot
创建影子根节点,其余的操作跟普通 DOM 操作没有太大区别。
<div class="widget">Hello, world!</div>
<script>
var host = document.querySelector('.widget');
var root = host.createShadowRoot();
var header = document.createElement('h1');
header.textContent = 'Hello, I am Shadow DOM.';
var paragraph = document.createElement('p');
paragraph.textContent = 'This is the content.';
root.appendChild(header);
root.appendChild(paragraph);
</script>
宿主节点的原有内容 Hello, world!
不会被渲染,取而代之的是 shadow root 里的内容。
使用 content 标签
<div class="widget">shadow dom</div>
<template>
<h1>Hello, I am <content></content></h1>
</template>
<script>
var host = document.querySelector('.widget');
var root = host.createShadowRoot();
var template = document.querySelector('template').content;
root.appendChild(document.importNode(template, true));
</script>
使用 <content>
标签,我们创建了一个插入,其将 .widget
中的文本投射出来,使之得以在我们的影子节点 <h1>
中展示。上面的例子最终渲染成 Hello, I am shadow dom
。
Shadow DOM 样式
Shadow DOM 和常规 DOM 之间存在一个边界,这个边界能防止常规 DOM 的样式泄露到 Shadow DOM 中来。
<style>
p.normal, p.shadow {
color: red;
font-size: 18px;
}
</style>
<p class="normal">我是一个普通文本</p>
<p class="shadow"></p>
<script>
var host = document.querySelector('.shadow');
var root = host.createShadowRoot();
root.innerHTML = `
<style>
p {
color: blue;
font-size: 24px;
}
</style>
<p>我是一个影子文本</p>`;
</script>
:host 选择器
通过 :host
选择器可以设置宿主元素的样式。
<style>
p {
color: red;
font-size: 18px;
}
</style>
<p class="normal">我是一个普通文本</p>
<p class="shadow"></p>
<script>
var host = document.querySelector('.shadow');
var root = host.createShadowRoot();
root.innerHTML = `
<style>
:host(p.shadow) {
color: blue;
font-size: 24px;
}
</style>
我是一个影子文本`;
</script>
注意上例中 shadow DOM 内的选择器是 :host(p.shadow)
,而不是跟外部平级的 :host(p)
。 因为:host(p)
的优先级低于外部的 p
选择器,所以不会生效。需要使用 :host(p.shadow)
提升优先级,才能将 .shadow
中的样式覆盖。
::shadow 伪类选择器
有时你可能会想让使用者打破影子边界的壁垒,让他们能够给你的组件添加一些样式,使用 ::shadow 伪类选择器我们可以赋予用户重写我们默认定义的自由。
<style>
p span,
p::shadow span {
color: red;
font-size: 18px;
}
</style>
<p class="normal"><span>我是一个普通文本</span></p>
<p class="shadow"></p>
<script>
var host = document.querySelector('.shadow');
var root = host.createShadowRoot();
root.innerHTML = `
<style>
span {
color: blue;
font-size: 24px;
}
</style>
<span>我是一个影子文本</span>`;
</script>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。