Web Components技術能夠把一組相關的HTML、JS代碼和CSS風格打包成爲一個自包含的組件,只要使用你們熟悉的標籤便可引入此組件。Web Components技術包括:html
四個分離而又互相關的四個構造塊。其中核心的即便是Custom Element、Shadow DOM,順便會講到而Template是一個支持技術。 HTML Import曾經被Chrome加入可是隨後和V0一塊兒被廢棄。這裏也不會討論它。git
定製元素能夠在原生元素外建立定製元素。定製元素是Web組件的一個基本構成塊。能夠在一個js文件內包含Custom Element須要的所有要素,包括HTML模板、CSS Style和ES6類。並使用一個HTML文件,引用此js文件從而可使用定製元素。github
假設咱們建立Spin Button,定製元素標籤爲:web
<spin-button value=「100」 step="10" min="50" max="150"></spin-button>
複製代碼
咱們首先實現此定製元素,可是爲了簡單起見,晚一點纔看它的屬性。此定製元素內部有一個加號按鈕,一個減號按鈕,一個span顯示當前值。那麼只須要把這個HTML模板組織、風格和代碼組合在一個文件內:瀏覽器
var template = `
<button inc>+</button><span>1</span><button dec>-</button>
<style>
span{color:red;}
*{font-size:2rem;}
</style>
`
class SpinButton extends HTMLElement{
connectedCallback(){
this.innerHTML = template
var b1 = this.querySelector('[inc]')
var b2 = this.querySelector('[dec]')
var s = this.querySelector('span')
var i = 1
b1.onclick = function(){
s.innerHTML = i++
}
b2.onclick = function(){
s.innerHTML = i--
}
}
}
customElements.define('spin-button',SpinButton)
複製代碼
而且建立一個index.html文件加載此文件,便可使用新的定製元素spin-button了:app
<script src="./spin.js"></script>
<spin-button></spin-button>
複製代碼
你能夠看到執行在瀏覽器內的界面上的兩個按鈕和一個span。建立一個定製元素有幾個要點:函數
這樣,咱們建立了一個獨特的定製元素,這個元素不在原生的瀏覽器標籤內。網站
定製元素就是這樣建立了,而且對於使用者來講,只要經過熟悉的元素標籤,便可引用一組帶有定製風格、操做和界面的組件了。this
可是此時的定製元素有一個問題,就是它內部定義的風格,不只僅會影響內部的元素,也會泄露到外部致使文檔也被影響,從而引起咱們不但願的邊際效應。好比在index.html內若是在文件尾部加入這樣的文本:google
<span>black</span>
複製代碼
你會發現black文本不是默認的顏色,而是紅色,這樣紅色來自於定製元素內部的風格定義代碼。若是但願隔離組件內的風格定義,那麼可使用Shaddow DOM技術。此主題會在下一部份內介紹。
Web建站使用組件技術有比較長的歷史了,這個技術一直以來都有一個挑戰,就是如何讓一個頁面可使用第三方控件,可是不會被此組件使用的CSS風格所影響。解決方案是CSS能夠局部化。想要組件內部的風格不會影響到外部,辦法就是使用Shadow DOM。Shadow DOM建立了一個隔離區,在這個隔離區內的DOM是獨立的,這意味着:
咱們拿前一個案例代碼作實驗,看看若是使用這個技術特性。
使用Shadow DOM的關鍵,是首先建立一個Shadow Node,整個組件內部的HTML片斷都插入到此節點內,而不是直接使用組件的innerHTML。咱們能夠在組件對象的構造器內執行此代碼:
class SpinButton extends HTMLElement{
constructor(){
super()
var shadow = this.attachShadow({mode:'open'})
var t = document.createElement('template')
t.innerHTML = template
shadow.appendChild(t.content.cloneNode(true))
}
}
複製代碼
執行後,你會發現span的風格再也不影響組件以外的標籤。看起來仍是很簡單的,只要把你原本須要構造的HTML內部DOM插入到shadow節點內便可。
元素的屬性被稱爲Attribute,JS對象內的屬性被稱爲Property。代碼慣例上每個Attribute都會有JS對象的一個Property對應。爲了方便,咱們但願添加的Attribute能夠和JS內的Property同步。就是說,若是有人經過HTML DOM API修改了Attribute,那麼我但願對於的JS屬性會被同步修改;反之亦然,有人修改了Property,那麼這個修改能夠會同步修改到對應的Attribute。
咱們以spin-button的value屬性爲例。定義一個普通的Property的方法是經過get/set關鍵字,好比定義value:
get value(){}
set value(newValue){}
複製代碼
隨後就可使用object.value
訪問此屬性值,或者經過object.value = newValue
爲屬性設置新值。能夠在兩個函數內經過代碼設置和Attribute同步:
get value(){
return this.getAttribute('value') || 1
}
set value(v){
this.setAttribute('value',v)
}
複製代碼
這樣代碼內經過對屬性value的訪問,最後都會致使對Attribute的訪問。若是有代碼對Attribute訪問,如何修改Attribute的同時同步更新Property呢。這就須要利用HTMLElement提供的生命週期方法了:
static get observedAttributes() {
return ['value'];
}
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case 'value':
break;
}
}
複製代碼
方法observedAttributes聽過返回值聲明須要觀察的屬性,這樣就能夠在指定屬性清單發生更新時經過另外一個生命週期方法attributeChangedCallback
,通知代碼變化的狀況。作響應的同步處理。整合後的代碼以下:
var template = `
<button inc>+</button><span>1</span><button dec>-</button>
<style>
span{color:red;}
*{font-size:2rem;}
</style>
`
class SpinButton extends HTMLElement{
constructor(){
super()
var shadow = this.attachShadow({mode:'open'})
var t = document.createElement('template')
t.innerHTML = template
shadow.appendChild(t.content.cloneNode(true))
var b1 = shadow.querySelector('[inc]')
var b2 = shadow.querySelector('[dec]')
this.s = shadow.querySelector('span')
var i = 1
var that = this
b1.onclick = function(){
that.s.innerHTML = ++that.value
}
b2.onclick = function(){
that.s.innerHTML = -- that.value
}
}
static get observedAttributes() {
return ['value'];
}
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case 'value':
this.s.innerHTML = newValue
break;
}
}
get value(){
return this.getAttribute('value') || 1
}
set value(v){
this.setAttribute('value',v)
}
}
customElements.define('spin-button',SpinButton)
複製代碼
組件給用戶使用的時候,通常會運行用戶傳遞特定的參數,以便讓組件更加符合本身的需求。
傳遞參數有幾種方法,一種是經過元素的屬性傳遞參數,通常的簡單值好比數字、日期和字符串就能夠此方式傳遞。另外就是容許傳遞HTML片斷,這樣能夠傳遞更加複雜的內容。這個方式使用的技術是有標準的,在Web Component標準內,被稱爲是slot插槽,也就是你們經常說到得內容分發技術。
咱們將會以Hello World爲案例,講述傳參的方法。假設一個標籤<greeting-hello>
,屬性傳參容許指定hello的對象,像是這樣:
<greeting-hello who="world">
<greeting-hello who="Reco">
複製代碼
Slot插槽傳參能夠傳遞複雜的HTML片斷,像是這樣:
<greeting-hello>
<b slot="who">Reco</b>
</greeting-hello>
複製代碼
經過對任何一個元素標記屬性slot,便可指定須要插入的HTML片斷和它的名字(這裏的片斷名字叫作who),而後能夠在定製元素內經過屬性傳遞參數已經談過了,這裏僅僅針對插槽傳遞`<slot>
引用此片斷:
<slot name="who"></slot>
複製代碼
有了插槽技術,就無需本身編寫代碼,方便的引入原本在使用組件的頁面內的HTML片斷。具體作法隨後描述。
和建立一個普通的定製元素並沒什麼區別,仍是同樣的如此:
<script type="module">
var template = `<h3>Hello,<slot name='who'/></h3>`
class GreetingHello extends HTMLElement{
constructor(){
super()
var shadow = this.attachShadow({mode:'open'})
var t = document.createElement('template')
t.innerHTML = template
shadow.appendChild(t.content.cloneNode(true))
}
}
customElements.define('greeting-hello',GreetingHello)
</script>
<greeting-hello><i slot="who">Reco</i></greeting-hello>
複製代碼
分發以後的效果等因而這樣的:
,<h3>Hello,<i>Reco</i></h3>
複製代碼
你會發現,在咱們本身的代碼中,沒有任何處理slot標籤的任何代碼。Web Components內部已經爲咱們實現了自動的內容分發。讓傳遞HTML片斷到組件內變成很是方便的事情。
Web Components的關鍵構成技術包括Custom Element和Shadow DOM,最先在Chrome實現,第一個版本被稱爲V0可是其餘瀏覽器沒有跟進,所以逐步被廢棄。本文討論的是V1版本。Firefox也已經實現了V1版本。 能夠在網站Whatcaniuse查詢當前支持狀態。