?
This document uses PHP Chinese website manual Release
概述
template標簽
基本用法
document.importNode()
Custom Element
document.registerElement()
添加屬性和方法
回調函數(shù)
Shadow DOM
HTML Import
基本操作
腳本的執(zhí)行
Web Component的封裝
Polymer.js
直接使用的組件
安裝
自定義組件
組件的使用方法
參考鏈接
各種網站往往需要一些相同的模塊,比如日歷、調色板等等,這種模塊就被稱為“組件”(component)。Web Component就是網頁組件式開發(fā)的技術規(guī)范。
采用組件進行網站開發(fā),有很多優(yōu)點。
(1)管理和使用非常容易。加載或卸載組件,只要添加或刪除一行代碼就可以了。
<link rel="import" href="my-dialog.htm"> <my-dialog heading="A Dialog">Lorem ipsum</my-dialog>
上面代碼加載了一個對話框組件。
(2)定制非常容易。組件往往留出接口,供使用者設置常見屬性,比如上面代碼的heading屬性,就是用來設置對話框的標題。
(3)組件是模塊化編程思想的體現(xiàn),非常有利于代碼的重用。標準格式的模塊,可以跨平臺、跨框架使用,構建、部署和與其他UI元素互動都有統(tǒng)一做法。
(4)組件提供了HTML、CSS、JavaScript封裝的方法,實現(xiàn)了與同一頁面上其他代碼的隔離。
未來的網站開發(fā),可以像搭積木一樣,把組件合在一起,就組成了一個網站。這是非常誘人的。
Web Components不是單一的規(guī)范,而是一系列的技術組成,包括Template、Custom Element、Shadow DOM、HTML Import四種技術規(guī)范。使用時,并不一定這四者都要用到。其中,Custom Element和Shadow DOM最重要,Template和HTML Import只起到輔助作用。
template標簽表示網頁中某些重復出現(xiàn)的部分的代碼模板。它存在于DOM之中,但是在頁面中不可見。
下面的代碼用來檢查,瀏覽器是否支持template標簽。
function supportsTemplate() { return 'content' in document.createElement('template'); } if (supportsTemplate()) { // 支持 } else { // 不支持 }
下面是一個模板的例子。
<template id="profileTemplate"> <div class="profile"> <img src="" class="profile__img"> <div class="profile__name"></div> <div class="profile__social"></div> </div> </template>
使用的時候,需要用JavaScript在模板中插入內容,然后將其插入DOM。
var template = document.querySelector('#profileTemplate'); template.querySelector('.profile__img').src = 'profile.jpg'; template.querySelector('.profile__name').textContent = 'Barack Obama'; template.querySelector('.profile__social').textContent = 'Follow me on Twitter'; document.body.appendChild(template.content);
上面的代碼是將模板直接插入DOM,更好的做法是克隆template節(jié)點,然后將克隆的節(jié)點插入DOM。這樣做可以多次使用模板。
var clone = document.importNode(template.content, true); document.body.appendChild(clone);
接受template插入的元素,叫做宿主元素(host)。在template之中,可以對宿主元素設置樣式。
<template> <style> :host { background: #f8f8f8; } :host(:hover) { background: #ccc; } </style> </template>
document.importNode方法用于克隆外部文檔的DOM節(jié)點。
var iframe = document.getElementsByTagName("iframe")[0]; var oldNode = iframe.contentWindow.document.getElementById("myNode"); var newNode = document.importNode(oldNode, true); document.getElementById("container").appendChild(newNode);
上面例子是將iframe窗口之中的節(jié)點oldNode,克隆進入當前文檔。
注意,克隆節(jié)點之后,還必須用appendChild方法將其加入當前文檔,否則不會顯示。換個角度說,這意味著插入外部文檔節(jié)點之前,必須用document.importNode方法先將這個節(jié)點準備好。
document.importNode方法接受兩個參數(shù),第一個參數(shù)是外部文檔的DOM節(jié)點,第二個參數(shù)是一個布爾值,表示是否連同子節(jié)點一起克隆,默認為false。大多數(shù)情況下,必須顯式地將第二個參數(shù)設為true。
HTML預定義的網頁元素,有時并不符合我們的需要,這時可以自定義網頁元素,這就叫做Custom Element。它是Web component技術的核心。舉例來說,你可以自定義一個叫做super-button的網頁元素。
<super-button></super-button>
注意,自定義網頁元素的標簽名必須含有連字符(-),一個或多個都可。這是因為瀏覽器內置的的HTML元素標簽名,都不含有連字符,這樣可以做到有效區(qū)分。
下面的代碼用于測試瀏覽器是否支持自定義元素。
if ('registerElement' in document) { // 支持 } else { // 不支持 }
使用自定義元素前,必須用document對象的registerElement方法登記該元素。該方法返回一個自定義元素的構造函數(shù)。
var SuperButton = document.registerElement('super-button'); document.body.appendChild(new SuperButton());
上面代碼生成自定義網頁元素的構造函數(shù),然后通過構造函數(shù)生成一個實例,將其插入網頁。
可以看到,document.registerElement方法的第一個參數(shù)是一個字符串,表示自定義的網頁元素標簽名。該方法還可以接受第二個參數(shù),表示自定義網頁元素的原型對象。
var MyElement = document.registerElement('user-profile', { prototype: Object.create(HTMLElement.prototype) });
上面代碼注冊了自定義元素user-profile。第二個參數(shù)指定該元素的原型為HTMLElement.prototype(瀏覽器內部所有Element節(jié)點的原型)。
但是,如果寫成上面這樣,自定義網頁元素就跟普通元素沒有太大區(qū)別。自定義元素的真正優(yōu)勢在于,可以自定義它的API。
var buttonProto = Object.create(HTMLElement.prototype); buttonProto.print = function() { console.log('Super Button!'); } var SuperButton = document.registerElement('super-button', { prototype: buttonProto }); var supperButton = document.querySelector('super-button'); supperButton.print(); supperButton.print();
上面代碼在原型對象上定義了一個print方法,然后將其指定為super-button元素的原型。因此,所有supper-button實例都可以調用print這個方法。
如果想讓自定義元素繼承某種特定的網頁元素,就要指定extends屬性。比如,想讓自定義元素繼承h1元素,需要寫成下面這樣。
var MyElement = document.registerElement('another-heading', { prototype: Object.create(HTMLElement.prototype), extends: 'h1' });
另一個是自定義按鈕(button)元素的例子。
var MyButton = document.registerElement('super-button', { prototype: Object.create(HTMLButtonElement.prototype), extends: 'button' });
如果要繼承一個自定義元素(比如x-foo-extended
繼承x-foo
),也是采用extends屬性。
var XFooExtended = document.registerElement('x-foo-extended', { prototype: Object.create(HTMLElement.prototype), extends: 'x-foo' });
定義了自定義元素以后,使用的時候,有兩種方法。一種是直接使用,另一種是間接使用,指定為某個現(xiàn)有元素是自定義元素的實例。
<!-- 直接使用 --> <supper-button></supper-button> <!-- 間接使用 --> <button is="supper-button"></button>
總之,如果A元素繼承了B元素。那么,B元素的is屬性,可以指定B元素是A元素的一個實例。
自定義元素的強大之處,就是可以在它上面定義新的屬性和方法。
var XFooProto = Object.create(HTMLElement.prototype); var XFoo = document.registerElement('x-foo', {prototype: XFooProto});
上面代碼注冊了一個x-foo標簽,并且指明原型繼承HTMLElement.prototype?,F(xiàn)在,我們就可以在原型上面,添加新的屬性和方法。
// 添加屬性 Object.defineProperty(XFooProto, "bar", {value: 5}); // 添加方法 XFooProto.foo = function() { console.log('foo() called'); }; // 另一種寫法 var XFoo = document.registerElement('x-foo', { prototype: Object.create(HTMLElement.prototype, { bar: { get: function() { return 5; } }, foo: { value: function() { console.log('foo() called'); } } }) });
自定義元素的原型有一些屬性,用來指定回調函數(shù),在特定事件發(fā)生時觸發(fā)。
createdCallback:實例生成時觸發(fā)
attachedCallback:實例插入HTML文檔時觸發(fā)
detachedCallback:實例從HTML文檔移除時觸發(fā)
attributeChangedCallback(attrName, oldVal, newVal):實例的屬性發(fā)生改變時(添加、移除、更新)觸發(fā)
下面是一個例子。
var proto = Object.create(HTMLElement.prototype); proto.createdCallback = function() { console.log('created'); this.innerHTML = 'This is a my-demo element!'; }; proto.attachedCallback = function() { console.log('attached'); }; var XFoo = document.registerElement('x-foo', {prototype: proto});
利用回調函數(shù),可以方便地在自定義元素中插入HTML語句。
var XFooProto = Object.create(HTMLElement.prototype); XFooProto.createdCallback = function() { this.innerHTML = "<b>I'm an x-foo-with-markup!</b>"; }; var XFoo = document.registerElement('x-foo-with-markup', {prototype: XFooProto});
上面代碼定義了createdCallback回調函數(shù),生成實例時,該函數(shù)運行,插入如下的HTML語句。
<x-foo-with-markup> <b>I'm an x-foo-with-markup!</b> </x-foo-with-markup>
所謂Shadow DOM指的是,瀏覽器將模板、樣式表、屬性、JavaScript代碼等,封裝成一個獨立的DOM元素。外部的設置無法影響到其內部,而內部的設置也不會影響到外部,與瀏覽器處理原生網頁元素(比如元素)的方式很像。Shadow DOM最大的好處有兩個,一是可以向用戶隱藏細節(jié),直接提供組件,二是可以封裝內部樣式表,不會影響到外部。Chrome 35+支持Shadow DOM。
Shadow DOM元素必須依存在一個現(xiàn)有的DOM元素之下,通過createShadowRoot方法創(chuàng)造,然后將其插入該元素。
var shadowRoot = element.createShadowRoot(); shadowRoot.appendChild(document.body);
上面代碼創(chuàng)造了一個shadowRoot元素,然后將其插入HTML文檔。
下面的例子是指定網頁中某個現(xiàn)存的元素,作為Shadom DOM的根元素。
<button>Hello, world!</button> <script> var host = document.querySelector('button'); var root = host.createShadowRoot(); root.textContent = '你好'; </script>
上面代碼指定現(xiàn)存的button元素,為Shadow DOM的根元素,并將button的文字從英文改為中文。
通過innerHTML屬性,可以為Shadow DOM指定內容。
var shadow = document.querySelector('#hostElement').createShadowRoot(); shadow.innerHTML = '<p>Here is some new text</p>'; shadow.innerHTML += '<style>p { color: red };</style>';
下面的例子是為Shadow DOM加上獨立的模板。
<div id="nameTag">張三</div> <template id="nameTagTemplate"> <style> .outer { border: 2px solid brown; } </style> <div class="outer"> <div class="boilerplate"> Hi! My name is </div> <div class="name"> Bob </div> </div> </template>
上面代碼是一個div元素和模板。接下來,就是要把模板應用到div元素上。
var shadow = document.querySelector('#nameTag').createShadowRoot(); var template = document.querySelector('#nameTagTemplate'); shadow.appendChild(template.content.cloneNode());
上面代碼先用createShadowRoot方法,對div創(chuàng)造一個根元素,用來指定Shadow DOM,然后把模板元素添加為Shadow的子元素。
長久以來,網頁可以加載外部的樣式表、腳本、圖片、多媒體,卻無法方便地加載其他網頁,iframe和ajax都只能提供部分的解決方案,且有很大的局限。HTML Import就是為了解決加載外部網頁這個問題,而提出來的。
下面代碼用于測試當前瀏覽器是否支持HTML Import。
function supportsImports() { return 'import' in document.createElement('link'); } if (supportsImports()) { // 支持 } else { // 不支持 }
HTML Import用于將外部的HTML文檔加載進當前文檔。我們可以將組件的HTML、CSS、JavaScript封裝在一個文件里,然后使用下面的代碼插入需要使用該組件的網頁。
<link rel="import" href="dialog.html">
上面代碼在網頁中插入一個對話框組件,該組建封裝在dialog.html
文件。注意,dialog.html文件中的樣式和JavaScript腳本,都對所插入的整個網頁有效。
假定A網頁通過HTML Import加載了B網頁,即B是一個組件,那么B網頁的樣式表和腳本,對A網頁也有效(準確得說,只有style標簽中的樣式對A網頁有效,link標簽加載的樣式表對A網頁無效)。所以可以把多個樣式表和腳本,都放在B網頁中,都從那里加載。這對大型的框架,是很方便的加載方法。
如果B與A不在同一個域,那么A所在的域必須打開CORS。
<!-- example.com必須打開CORS --> <link rel="import" href="http://example.com/elements.html">
除了用link標簽,也可以用JavaScript調用link元素,完成HTML Import。
var link = document.createElement('link'); link.rel = 'import'; link.href = 'file.html' link.onload = function(e) {...}; link.onerror = function(e) {...}; document.head.appendChild(link);
HTML Import加載成功時,會在link元素上觸發(fā)load事件,加載失敗時(比如404錯誤)會觸發(fā)error事件,可以對這兩個事件指定回調函數(shù)。
<script async> function handleLoad(e) { console.log('Loaded import: ' + e.target.href); } function handleError(e) { console.log('Error loading import: ' + e.target.href); } </script> <link rel="import" href="file.html" onload="handleLoad(event)" onerror="handleError(event)">
上面代碼中,handleLoad和handleError函數(shù)的定義,必須在link元素的前面。因為瀏覽器元素遇到link元素時,立刻解析并加載外部網頁(同步操作),如果這時沒有對這兩個函數(shù)定義,就會報錯。
HTML Import是同步加載,會阻塞當前網頁的渲染,這主要是為了樣式表的考慮,因為外部網頁的樣式表對當前網頁也有效。如果想避免這一點,可以為link元素加上async屬性。當然,這也意味著,如果外部網頁定義了組件,就不能立即使用了,必須等HTML Import完成,才能使用。
<link rel="import" href="/path/to/import_that_takes_5secs.html" async>
但是,HTML Import不會阻塞當前網頁的解析和腳本執(zhí)行(即阻塞渲染)。這意味著在加載的同時,主頁面的腳本會繼續(xù)執(zhí)行。
最后,HTML Import支持多重加載,即被加載的網頁同時又加載其他網頁。如果這些網頁都重復加載同一個外部腳本,瀏覽器只會抓取并執(zhí)行一次該腳本。比如,A網頁加載了B網頁,它們各自都需要加載jQuery,瀏覽器只會加載一次jQuery。
外部網頁的內容,并不會自動顯示在當前網頁中,它只是儲存在瀏覽器中,等到被調用的時候才加載進入當前網頁。為了加載網頁網頁,必須用DOM操作獲取加載的內容。具體來說,就是使用link元素的import屬性,來獲取加載的內容。這一點與iframe完全不同。
var content = document.querySelector('link[rel="import"]').import;
發(fā)生以下情況時,link.import屬性為null。
瀏覽器不支持HTML Import
link元素沒有聲明
rel="import"
link元素沒有被加入DOM
link元素已經從DOM中移除
對方域名沒有打開CORS
下面代碼用于從加載的外部網頁選取id為template的元素,然后將其克隆后加入當前網頁的DOM。
var el = linkElement.import.querySelector('#template'); document.body.appendChild(el.cloneNode(true));
當前網頁可以獲取外部網頁,反過來也一樣,外部網頁中的腳本,不僅可以獲取本身的DOM,還可以獲取link元素所在的當前網頁的DOM。
// 以下代碼位于被加載(import)的外部網頁 // importDoc指向被加載的DOM var importDoc = document.currentScript.ownerDocument; // mainDoc指向主文檔的DOM var mainDoc = document; // 將子頁面的樣式表添加主文檔 var styles = importDoc.querySelector('link[rel="stylesheet"]'); mainDoc.head.appendChild(styles.cloneNode(true));
上面代碼將所加載的外部網頁的樣式表,添加進當前網頁。
被加載的外部網頁的腳本是直接在當前網頁的上下文執(zhí)行,因為它的window.document
指的是當前網頁的document,而且它定義的函數(shù)可以被當前網頁的腳本直接引用。
對于Web Component來說,HTML Import的一個重要應用是在所加載的網頁中,自動登記Custom Element。
<script> // 定義并登記<say-hi> var proto = Object.create(HTMLElement.prototype); proto.createdCallback = function() { this.innerHTML = 'Hello, <b>' + (this.getAttribute('name') || '?') + '</b>'; }; document.registerElement('say-hi', {prototype: proto}); </script> <template id="t"> <style> ::content > * { color: red; } </style> <span>I'm a shadow-element using Shadow DOM!</span> <content></content> </template> <script> (function() { var importDoc = document.currentScript.ownerDocument; //指向被加載的網頁 // 定義并登記<shadow-element> var proto2 = Object.create(HTMLElement.prototype); proto2.createdCallback = function() { var template = importDoc.querySelector('#t'); var clone = document.importNode(template.content, true); var root = this.createShadowRoot(); root.appendChild(clone); }; document.registerElement('shadow-element', {prototype: proto2}); })(); </script>
上面代碼定義并登記了兩個元素:和。在主頁面使用這兩個元素,非常簡單。
<head> <link rel="import" href="elements.html"> </head> <body> <say-hi name="Eric"></say-hi> <shadow-element> <div>( I'm in the light dom )</div> </shadow-element> </body>
不難想到,這意味著HTML Import使得Web Component變得可分享了,其他人只要拷貝elements.html
,就可以在自己的頁面中使用了。
Web Components是非常新的技術,為了讓老式瀏覽器也能使用,Google推出了一個函數(shù)庫Polymer.js。這個庫不僅可以幫助開發(fā)者,定義自己的網頁元素,還提供許多預先制作好的組件,可以直接使用。
Polymer.js提供的組件,可以直接插入網頁,比如下面的google-map。。
<script src="components/platform/platform.js"></script> <link rel="import" href="google-map.html"> <google-map lat="37.790" long="-122.390"></google-map>
再比如,在網頁中插入一個時鐘,可以直接使用下面的標簽。
<polymer-ui-clock></polymer-ui-clock>
自定義標簽與其他標簽的用法完全相同,也可以使用CSS指定它的樣式。
polymer-ui-clock { width: 320px; height: 320px; display: inline-block; background: url("../assets/glass.png") no-repeat; background-size: cover; border: 4px solid rgba(32, 32, 32, 0.3); }
如果使用bower安裝,至少需要安裝platform和core components這兩個核心部分。
bower install --save Polymer/platform bower install --save Polymer/polymer
你還可以安裝所有預先定義的界面組件。
bower install Polymer/core-elements bower install Polymer/polymer-ui-elements
還可以只安裝單個組件。
bower install Polymer/polymer-ui-accordion
這時,組件根目錄下的bower.json,會指明該組件的依賴的模塊,這些模塊會被自動安裝。
{ "name": "polymer-ui-accordion", "private": true, "dependencies": { "polymer": "Polymer/polymer#0.2.0", "polymer-selector": "Polymer/polymer-selector#0.2.0", "polymer-ui-collapsible": "Polymer/polymer-ui-collapsible#0.2.0" }, "version": "0.2.0" }
下面是一個最簡單的自定義組件的例子。
<link rel="import" href="../bower_components/polymer/polymer.html"> <polymer-element name="lorem-element"> <template> <p>Lorem ipsum</p> </template> </polymer-element>
上面代碼定義了lorem-element組件。它分成三個部分。
(1)import命令
import命令表示載入核心模塊
(2)polymer-element標簽
polymer-element標簽定義了組件的名稱(注意,組件名稱中必須包含連字符)。它還可以使用extends屬性,表示組件基于某種網頁元素。
<polymer-element name="w3c-disclosure" extends="button">
(3)template標簽
template標簽定義了網頁元素的模板。
在調用組件的網頁中,首先加載polymer.js庫和組件文件。
<script src="components/platform/platform.js"> </script><link rel="import" href="w3c-disclosure.html">
然后,分成兩種情況。如果組件不基于任何現(xiàn)有的HTML網頁元素(即定義的時候沒有使用extends屬性),則可以直接使用組件。
<lorem-element></lorem-element>
這時網頁上就會顯示一行字“Lorem ipsum”。
如果組件是基于(extends)現(xiàn)有的網頁元素,則必須在該種元素上使用is屬性指定組件。
<button is="w3c-disclosure">Expand section 1</button>
Dominic Cooney, Shadow DOM 101
Eric Bidelman, HTML's New Template Tag
Rey Bango, Using Polymer to Create Web Components
Cédric Trévisan, Building an Accessible Disclosure Button – using Web Components](http://blog.paciellogroup.com/2014/06/accessible-disclosure-button-using-web-components/)
Eric Bidelman, Custom Elements: defining new elements in HTML
Eric Bidelman, HTML Imports
TJ VanToll, Why Web Components Are Ready For Production
Chris Bateman, A No-Nonsense Guide to Web Components, Part 1: The Specs