什麼是shadow dom?
首先我們先來看看它長什麼樣子。在HTML5中,我們只用寫如下簡單的兩行程式碼,就可以通過 <video> 標籤來建立一個瀏覽器自帶的視訊播放器控制元件。
<video controls=""> <source src="https://mdn.mozillademos.org/files/2587/AudioTest%20(1).ogg" type="audio/ogg"> </video>
而在各個瀏覽器中,它都有各不相同的外觀展現,例如chrome中它長下面這樣:
但為什麼我們在dom中無法看到他們內部的結構?噢,實際上瀏覽器通過某種技術把它們隱藏起來而已,但我們可以通過 DevTools 設定中勾選Show user agent shadow DOM來看到這些結構。勾選後我們發現在 <video> 標籤下多了 #shadow-root(user-agent) 這個東西,它包含了 video 控制元件的內部dom結構,這就是所謂的shadow dom了。
並且shadow dom為我們提供了簡單有效的樣式隔離。如果你嘗試通過 input[type="button"] { display:none; } 之類的樣式來影響控制元件外觀,你會發現是無效的。這樣如果我們通過 shadow dom 來隱藏控制元件的內部實現,就不用擔心對樣式表進行修改時對控制元件造成影響了。
下面我們來看看如何通過shadow dom來封裝控制元件。
使用Shadow Dom封裝控制元件
首先我們先看看w3c對shadow dom的描述:
A shadow host is an element that hosts one or more node trees.
A shadow tree is a node tree hosted by a shadow host.
A shadow root is the root node of a shadow tree.
這三句話告訴我們:
- - shadow dom 由一顆或多顆 shadow tree 組成,並且需要一個普通的dom元素作為它的容器
- - shadow tree 是一顆節點樹
- - shadow root 是shadow tree的根節點
下面,我們直接通過一個例子來演示如何使用shadow dom來封裝web元件:
DOM:
// 模板 <template id="wgTemplate"> <style> color: #fff; background: #006dcc; </style> <button type="button" class="btn">button widget</button> </template> // shadow dom 容器 <div id="my-widget"></div>
JS:
// 建立shadow dom <script type='text/javascript'> var host = document.querySelector("#my-widget") var tp = document.querySelector("#wgTemplate") var clone = document.importNode(tp.content, true) host.createShadowRoot().appendChild(clone) </script>
- 第一步,我們需要一個shadow dom的容器 <div id="my-widget"></div>
- 隨後通過 document.importNode() 獲取到控制元件的模板並克隆
- 接著使用 createShadowRoot() 要建立一個shadow root作為shadow tree的根節點
- 最後將將模板內容 clone 作為shadow tree插入到shadow root中
開啟頁面,我們可以看到如下結構,這就是一個利用shadow隱藏控制元件內部實現最簡單的例子了。
接著我們還可以結合上一篇文章《web component之custom element》的建立自定義元素的方法來封裝自定義控制元件:
dom:
// 模板 <template id="wgTemplate"> <style> .btn { color: #fff; background: #006dcc; } </style> <button type="button" class="btn">custom button widget</button> </template>
js:
// 建立自定義控制元件 <script type='text/javascript'> var proto = Object.create(HTMLElement.prototype, { createdCallback: { value: function() { var tp = document.querySelector('#wgTemplate') var clone = document.importNode(tp.content, true) this.createShadowRoot().appendChild(clone) } } }) var MyButtom = document.registerElement('my-buttom', {prototype: proto}) document.body.appendChild(new MyButtom()) </script>