Web Component探索

概述

各類網站每每須要一些相同的模塊,好比日曆、調色板等等,這種模塊就被稱爲「組件」(component)。Web Component就是網頁組件式開發的技術規範。javascript

採用組件進行網站開發,有不少優勢。css

(1)管理和使用很是容易。加載或卸載組件,只要添加或刪除一行代碼就能夠了。html

<link rel="import" href="my-dialog.htm"> <my-dialog heading="A Dialog">Lorem ipsum</my-dialog> 

上面代碼加載了一個對話框組件。java

(2)定製很是容易。組件每每留出接口,供使用者設置常見屬性,好比上面代碼的heading屬性,就是用來設置對話框的標題。node

(3)組件是模塊化編程思想的體現,很是有利於代碼的重用。標準格式的模塊,能夠跨平臺、跨框架使用,構建、部署和與其餘UI元素互動都有統一作法。web

(4)組件提供了HTML、CSS、JavaScript封裝的方法,實現了與同一頁面上其餘代碼的隔離。ajax

將來的網站開發,能夠像搭積木同樣,把組件合在一塊兒,就組成了一個網站。這是很是誘人的。編程

Web Components不是單一的規範,而是一系列的技術組成,包括Template、Custom Element、Shadow DOM、HTML Import四種技術規範。使用時,並不必定這四者都要用到。其中,Custom Element和Shadow DOM最重要,Template和HTML Import只起到輔助做用。json

template標籤

基本用法

template標籤表示網頁中某些重複出現的部分的代碼模板。它存在於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.content.querySelector('.profile__img').src = 'profile.jpg'; template.content.querySelector('.profile__name').textContent = 'Barack Obama'; template.content.querySelector('.profile__social').textContent = 'Follow me on Twitter'; document.body.appendChild(template.content); 

上面的代碼是將模板直接插入DOM,更好的作法是克隆template節點,而後將克隆的節點插入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()

document.importNode方法用於克隆外部文檔的DOM節點。

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窗口之中的節點oldNode,克隆進入當前文檔。

注意,克隆節點以後,還必須用appendChild方法將其加入當前文檔,不然不會顯示。換個角度說,這意味着插入外部文檔節點以前,必須用document.importNode方法先將這個節點準備好。

document.importNode方法接受兩個參數,第一個參數是外部文檔的DOM節點,第二個參數是一個布爾值,表示是否連同子節點一塊兒克隆,默認爲false。大多數狀況下,必須顯式地將第二個參數設爲true。

Custom Element

HTML預約義的網頁元素,有時並不符合咱們的須要,這時能夠自定義網頁元素,這就叫作Custom Element。它是Web component技術的核心。舉例來講,你能夠自定義一個叫作super-button的網頁元素。

<super-button></super-button> 

注意,自定義網頁元素的標籤名必須含有連字符(-),一個或多個均可。這是由於瀏覽器內置的的HTML元素標籤名,都不含有連字符,這樣能夠作到有效區分。

下面的代碼用於測試瀏覽器是否支持自定義元素。

if ('registerElement' in document) { // 支持 } else { // 不支持 } 

document.registerElement()

使用自定義元素前,必須用document對象的registerElement方法登記該元素。該方法返回一個自定義元素的構造函數。

var SuperButton = document.registerElement('super-button'); document.body.appendChild(new SuperButton()); 

上面代碼生成自定義網頁元素的構造函數,而後經過構造函數生成一個實例,將其插入網頁。

能夠看到,document.registerElement方法的第一個參數是一個字符串,表示自定義的網頁元素標籤名。該方法還能夠接受第二個參數,表示自定義網頁元素的原型對象。

var MyElement = document.registerElement('user-profile', { prototype: Object.create(HTMLElement.prototype) }); 

上面代碼註冊了自定義元素user-profile。第二個參數指定該元素的原型爲HTMLElement.prototype(瀏覽器內部全部Element節點的原型)。

可是,若是寫成上面這樣,自定義網頁元素就跟普通元素沒有太大區別。自定義元素的真正優點在於,能夠自定義它的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(); 

上面代碼在原型對象上定義了一個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' }); 

定義了自定義元素之後,使用的時候,有兩種方法。一種是直接使用,另外一種是間接使用,指定爲某個現有元素是自定義元素的實例。

<!-- 直接使用 --> <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。如今,咱們就能夠在原型上面,添加新的屬性和方法。

// 添加屬性 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'); } } }) }); 

回調函數

自定義元素的原型有一些屬性,用來指定回調函數,在特定事件發生時觸發。

  • createdCallback:實例生成時觸發
  • attachedCallback:實例插入HTML文檔時觸發
  • detachedCallback:實例從HTML文檔移除時觸發
  • attributeChangedCallback(attrName, oldVal, newVal):實例的屬性發生改變時(添加、移除、更新)觸發

下面是一個例子。

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}); 

利用回調函數,能夠方便地在自定義元素中插入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回調函數,生成實例時,該函數運行,插入以下的HTML語句。

<x-foo-with-markup> <b>I'm an x-foo-with-markup!</b> </x-foo-with-markup> 

Shadow DOM

所謂Shadow DOM指的是,瀏覽器將模板、樣式表、屬性、JavaScript代碼等,封裝成一個獨立的DOM元素。外部的設置沒法影響到其內部,而內部的設置也不會影響到外部,與瀏覽器處理原生網頁元素(好比<video>元素)的方式很像。Shadow DOM最大的好處有兩個,一是能夠向用戶隱藏細節,直接提供組件,二是能夠封裝內部樣式表,不會影響到外部。Chrome 35+支持Shadow DOM。

Shadow DOM元素必須依存在一個現有的DOM元素之下,經過createShadowRoot方法創造,而後將其插入該元素。

var shadowRoot = element.createShadowRoot(); document.body.appendChild(shadowRoot); 

上面代碼創造了一個shadowRoot元素,而後將其插入HTML文檔。

下面的例子是指定網頁中某個現存的元素,做爲Shadow DOM的根元素。

<button>Hello, world!</button> <script> var host = document.querySelector('button'); var root = host.createShadowRoot(); root.textContent = '你好'; </script> 

上面代碼指定現存的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(true)); 

上面代碼先用createShadowRoot方法,對div創造一個根元素,用來指定Shadow DOM,而後把模板元素添加爲Shadow的子元素。

HTML Import

基本操做

長久以來,網頁能夠加載外部的樣式表、腳本、圖片、多媒體,卻沒法方便地加載其餘網頁,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元素上觸發load事件,加載失敗時(好比404錯誤)會觸發error事件,能夠對這兩個事件指定回調函數。

<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函數的定義,必須在link元素的前面。由於瀏覽器元素遇到link元素時,馬上解析並加載外部網頁(同步操做),若是這時沒有對這兩個函數定義,就會報錯。

HTML Import是同步加載,會阻塞當前網頁的渲染,這主要是爲了樣式表的考慮,由於外部網頁的樣式表對當前網頁也有效。若是想避免這一點,能夠爲link元素加上async屬性。固然,這也意味着,若是外部網頁定義了組件,就不能當即使用了,必須等HTML Import完成,才能使用。

<link rel="import" href="/path/to/import_that_takes_5secs.html" async> 

可是,HTML Import不會阻塞當前網頁的解析和腳本執行(即阻塞渲染)。這意味着在加載的同時,主頁面的腳本會繼續執行。

最後,HTML Import支持多重加載,即被加載的網頁同時又加載其餘網頁。若是這些網頁都重複加載同一個外部腳本,瀏覽器只會抓取並執行一次該腳本。好比,A網頁加載了B網頁,它們各自都須要加載jQuery,瀏覽器只會加載一次jQuery。

腳本的執行

外部網頁的內容,並不會自動顯示在當前網頁中,它只是儲存在瀏覽器中,等到被調用的時候才加載進入當前網頁。爲了加載網頁網頁,必須用DOM操做獲取加載的內容。具體來講,就是使用link元素的import屬性,來獲取加載的內容。這一點與iframe徹底不一樣。

var content = document.querySelector('link[rel="import"]').import; 

發生如下狀況時,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)); 

上面代碼將所加載的外部網頁的樣式表,添加進當前網頁。

被加載的外部網頁的腳本是直接在當前網頁的上下文執行,由於它的window.document指的是當前網頁的document,並且它定義的函數能夠被當前網頁的腳本直接引用。

Web Component的封裝

對於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> 

上面代碼定義並登記了兩個元素:<say-hi>和<shadow-element>。在主頁面使用這兩個元素,很是簡單。

<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,就能夠在本身的頁面中使用了。

Polymer.js

Web Components是很是新的技術,爲了讓老式瀏覽器也能使用,Google推出了一個函數庫Polymer.js。這個庫不只能夠幫助開發者,定義本身的網頁元素,還提供許多預先製做好的組件,能夠直接使用。

直接使用的組件

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"> 

而後,分紅兩種狀況。若是組件不基於任何現有的HTML網頁元素(即定義的時候沒有使用extends屬性),則能夠直接使用組件。

<lorem-element></lorem-element> 

這時網頁上就會顯示一行字「Lorem ipsum」。

若是組件是基於(extends)現有的網頁元素,則必須在該種元素上使用is屬性指定組件。

<button is="w3c-disclosure">Expand section 1</button>
相關文章
相關標籤/搜索