組件的概念
組件,是數(shù)據(jù)和方法的一個(gè)封裝,其定義了一個(gè)可重用的軟件元素的功能,展示和使用,通常表現(xiàn)為一個(gè)或一組可重用的元素。
組件的特性
可拓展性:既然組件是針對(duì)某一特定功能或需求開發(fā)的,那它就必須易于開發(fā)和拓展;
封裝性:組件作為一個(gè)獨(dú)立整體供使用,應(yīng)該是對(duì)內(nèi)修改,對(duì)外封閉,只供使用,而不對(duì)使用環(huán)境產(chǎn)生副作用;
易用性:組件的目的是產(chǎn)生可重用的獨(dú)立部件,那就必須提供一種簡(jiǎn)單快捷的方式供使用。
Web Components特征
Web Components將一系列特性加入HTML和DOM規(guī)范,使得開發(fā)者可以自由創(chuàng)建在web應(yīng)用或文檔可重用的元素或部件,其由四部分組成:
HTML模板(HTML Templates):HTML內(nèi)的DOM模板,在<template>元素內(nèi)聲明。
影子DOM(Shadow DOM):組合對(duì)DOM和樣式的封裝;
HTML導(dǎo)入(HTML Imports):定義在文檔中導(dǎo)入其他HTML文檔的方式;
自定義元素(Custom Elements):定義新HTML元素的一系列API;
HTML模板
<body>
<hello-button>hello</hello-button>
</body>
<script>
(function() {
var HelloButtonPrototype = Object.create(HTMLElement.prototype);
HelloButtonPrototype.who = function() {
alert('Hello!');
}
HelloButtonPrototype.createdCallback = function() {
this.addEventListener('click', function(e) {
HelloButtonPrototype.who();
});
}
document.registerElement('hello-button', {prototype: HelloButtonPrototype});
})();
</script>
影子DOM(Shadow DOM)
DOM,即文檔對(duì)象模型,是HTML文檔的一個(gè)結(jié)構(gòu)表示,以樹形結(jié)構(gòu)表示一個(gè)文檔,文檔中元素間關(guān)系按照父子,兄弟關(guān)系排列;DOM規(guī)范提供一系列API支持我們操作文檔節(jié)點(diǎn),即通常所說(shuō)的DOM API。
前面提到Web Components指封裝DOM和樣式,以組件的形式在文檔中使用,而不同于JavaScript中函數(shù)會(huì)形成一個(gè)單獨(dú)作用域,文檔DOM樹的層次結(jié)構(gòu)中是不存在局部作用域概念的,也就是說(shuō)文檔內(nèi)所有定義的樣式都對(duì)整個(gè)文檔產(chǎn)生影響,文檔中的樣式也會(huì)影響組件內(nèi)的聲明樣式,而不限定于元素所處位置,這樣顯然極大阻礙了組件的獨(dú)立性和可重用性,是必須要解決的問(wèn)題,不過(guò)不用擔(dān)心,這都已經(jīng)解決了,解決方案就是下文介紹的attachShadow()方法。
影子DOM API提供了attachShadow()方法,創(chuàng)建一個(gè)影子DOM,支持將封裝的內(nèi)容或組件作為一個(gè)獨(dú)立DOM子樹附加進(jìn)一個(gè)HTML文檔,組件內(nèi)與外部隔離,樣式互不影響,這也印證了組件開發(fā)的封裝性需求。
創(chuàng)建
要?jiǎng)?chuàng)建一個(gè)影子DOM,很簡(jiǎn)單,使用attachShadow()方法即可,而需要注意的是所有影子DOM必須和一個(gè)文檔中存在的元素(HTML內(nèi)置元素或自定義元素)綁定,才能使用:
var frag = document.createElement('div');
var shadowRoot = frag.attachShadow({mode: 'open'});
// open 可以通過(guò)javascript來(lái)獲取shandow DOM 例如
// close的話,就是不可以在外部獲取到shadow Dom 了,訪問(wèn)shadowRoot就會(huì)返回null
shadowRoot.innerHTML = '<p>Shadow DOM Content</p>';
// class寫法
class SlotTest extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
connectedCallback() {
}
}
customElements.define('slot-test', SlotTest);
影子樹(SHADOW TREE)與影子主體(SHADOW HOST)
上文使用attachShadow()方法創(chuàng)建的元素就是一個(gè)影子DOM,而其子內(nèi)容就構(gòu)成一棵影子樹(shadow tree),而和影子DOM綁定,也就是包含該樹的文檔內(nèi)元素通常稱為影子主體(shadow host)。
template 槽位(SLOT)
<div class="menus">
<h2>Menus</h2>
<slot></slot>
</div>
使用:
<slot-test>
<ul>
<li>home</li>
<li>order</li>
<li>about</li>
</ul>
</slot-test>
渲染結(jié)果:
<div class="menus">
<h2>Menus</h2>
<ul>
<li>Home</li>
<li>order</li>
<li>about</li>
</ul>
</div>
命名槽
<div class="menus">
<h2>Menus</h2>
<slot name="menu"></slot>
</div>
<slot-test>
<ul>
<li>home</li>
<li>order</li>
<li>about</li>
</ul>
<ul slot="menu">
<li>menu home</li>
<li>menu order</li>
<li>menu about</li>
</ul>
</slot-test>
渲染結(jié)果
<div class="menus">
<h2>Menus</h2>
<ul>
<li>menu home</li>
<li>menu order</li>
<li>menu about</li>
</ul>
</div>
樣式
Web Components定義的組件內(nèi)的樣式與外部環(huán)境的樣式是互不影響的,那么如何為組件設(shè)置樣式呢,依然使用<style>標(biāo)簽:
class SlotTest extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<style>
div h2 {
color: red;
}
</style>
<div>
<h2>Menus</h2>
<slot></slot>
</div>
`
}
connectedCallback() {
}
}
customElements.define('slot-test', SlotTest);
HTML引入(HTML Imports)
<link rel="import" href="components.html">
自定義元素(Custom Elements)
自定義元素支持開發(fā)者定義一類新HTML元素,聲明其行為和樣式,自定義元素分兩類:
自定義標(biāo)簽元素(Autonomous custom elements):完全獨(dú)立于原始HTML元素標(biāo)簽的新標(biāo)簽元素,其所有行為需要開發(fā)者定義;
自定義內(nèi)置元素(Customized built-in):基于HTML原始元素標(biāo)簽的自定義元素,以便于使用原始元素的特性,開發(fā)者只需要定義拓展行為;
創(chuàng)建自定義標(biāo)簽元素
為了創(chuàng)建一個(gè)自定義標(biāo)簽元素,我們需要繼承HTMLELement類, 如在很多頁(yè)面我們經(jīng)常會(huì)有一鍵回到頁(yè)面頂部功能,我們創(chuàng)建一個(gè)返回頂部的組件:
class GoTop extends HTMLElement {
constructor() {
super();
this.addClick()
}
addClick() {
this.addEventListener('click',function(e) {
console.log(999);
(function smoothscroll(){
var currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
if (currentScroll > 0) {
window.requestAnimationFrame(smoothscroll);
window.scrollTo (0,currentScroll - (currentScroll/5));
}
})();
})
}
connectedCallback() {
// 首次被插入文檔DOM時(shí),被調(diào)用
console.log(88888)
console.log(this.getAttribute('data-name'))
}
disconnectedCallback() {
// 文檔DOM中刪除時(shí),被調(diào)用
}
adoptedCallback() {
// 被移動(dòng)到新的文檔時(shí),被調(diào)用
}
attributesChangedCallback() {
// 元素的屬性被增刪改時(shí)執(zhí)行
}
}
console.log(HTMLElement.prototype)
customElements.define('go-top', GoTop);
生命周期函數(shù)github example:
https://github.com/mdn/web-components-examples/tree/master/life-cycle-callbacks
在需要使用該組件的頁(yè)面只需像使用正常HTML元素一樣:
<go-top>Top</go-top>
該元素的一切樣式,行為,事件監(jiān)聽,默認(rèn)行為均需要開發(fā)者自行定義
//帶圖片及l(fā)ink的組件
class ImgLink extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
let src = this.getAttribute('src');
let height = this.getAttribute('height') || '100';
let width = this.getAttribute('width') || '100';
// Create a shadow root
let shadow = this.attachShadow({mode: 'open'});
// create img
let img = document.createElement('img');
img.src = src;
img.width = width;
img.height = height;
shadow.appendChild(img)
// create a
let link = document.createElement('a');
link.href = this.getAttribute('url');
link.innerText = this.getAttribute('name')
shadow.appendChild(link)
}
}
customElements.define('img-link', ImgLink);
使用
<img-link width="100" height="100" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4621/javascript.png" url="http://www.w3school.com.cn/js/index.asp" name="javascript"></img-link>