使用Shadow DOM創建Web組件
本文概述
Web Components(組件)標準是一系列***推出的標準,它可以被用來創建可被復用的Web部件,當頁面中所使用的Web部件被更新為新版本時不必修改 頁面中其他任何代碼。這里所說的部件,是一種可實現與用戶之間的交互的可視化組件,開發者可以使用HTML代碼與JavaScript腳本代碼來開發這些 部件。Web Componnts標準定義如何開發這些部件。
目前為止,由于一些基本問題,導致使用HTML代碼與JavaScript腳本代碼開發出來的部件很難被應用在頁面中,這些問題包括:一個部 件內的DOM樹并沒有被封裝,這意味著你的樣式表中的樣式可能被意外地被應用到部件中,你的JavaScript腳本代碼可能會修改部件中的某個部分,你 定義的ID可能會與部件內部所使用的ID相同等等。
最糟糕的是,由于部件沒有被封裝,如果你更新了部件,更改了其中的內部細節,你的頁面上的樣式表及JavaScript腳本代碼可能會導致意想不到的結果。
一個Web組件通常由四個部分組成:模板、Shadow DOM、自定義元素與打包,其中Shadow DOM解決了組件在頁面中的封裝問題。可以結合使用這四個部分,也可以單獨使用其中的一兩個部分。本文介紹如何使用Shadom DOM。目前為止只有Chrome 25瀏覽器支持Shadow DOM,且使用時必須書寫webkit前綴。
簡單示例程序
通過Shadow DOM的使用,元素可以擁有一種新的被稱為shadow root的節點,這時該元素被稱為shadow容器。瀏覽器中不會渲染shadow容器中原有內容,而是渲染shadow root節點中的內容。
例如,你可以將HTML頁面書寫為如下所示:
- <button>click me</button>
- <script>
- var host = document.querySelector('button');
- var root = host.webkitCreateShadowRoot();
- root.textContent = '點擊我';
- </script>
通過這段代碼,按鈕中原有文字“click me”將被替換為“點擊我”。請注意,在JavaScript腳本代碼中,按鈕的textContent屬性值仍然為“click me”,而不是“點擊我”,因為在DOM樹中shadow root節點是被忽視的。
一個容易被違反的規則是:你不應該將頁面內容安排在shadow root節點中。頁面內容必須是屏幕閱讀器、搜索引擎、瀏覽器擴展所能訪問到的內容。Shadow DOM從語義上來說是沒有任何意義的,它只被用來動態創建一個Web組件,而該Web組件中的任何內容也能被顯示在頁面中。當然,我們不是被強制使用該方 法來創建Web組件。
分離內容與展示
接下來,我們來看如何使用Shadow DOM將內容與展示進行分離。我們具有如下圖所示的一個Web組件。
其樣式代碼與HTML頁面代碼如下所示(不使用Shadow DOM):
- <style>
- .outer {
- border: 2px solid brown;
- border-radius: 1em;
- background: red;
- font-size: 20pt;
- width: 12em;
- height: 7em;
- text-align: center;
- }
- .boilerplate {
- color: white;
- font-family: sans-serif;
- padding: 0.5em;
- }
- .name {
- color: black;
- background: white;
- font-family: "宋體";
- font-size: 30pt;
- padding-top: 0.2em;
- }
- </style>
- <div class="outer">
- <div class="boilerplate">
- 你好,歡迎來到
- </div>
- <div class="name">
- HTML 5在線
- </div>
- </div>
因為這個Web組件沒有被封裝,其樣式代碼與HTML代碼是被直接書寫在樣式代碼與頁面HTML代碼中的,所以只要有人在其他地方不小心修改或重定義了該Web組件所使用的樣式類代碼,該組件就被破壞了。我們需要避免這種情況。
***步:隱藏展示細節
在這段代碼中,我們可能已經注意到:其中有一個樣式類名為name的div元素,其中顯示“HTML 5在線”文字。首先,我們將該元素的HTML代碼修改為如下所示:
- <div id="nameComponent">HTML 5在線</div>
然后,我們將所有該Web部件用樣式代碼與HTML代碼書寫到一個id為nameComponentTemplate的template元素中:
- <div id="nameComponent">HTML 5在線</div>
- <template id="nameComponentTemplate">
- <style>
- .outer {
- border: 2px solid brown;
- border-radius: 1em;
- background: red;
- font-size: 20pt;
- width: 12em;
- height: 7em;
- text-align: center;
- }
- .boilerplate {
- color: white;
- font-family: sans-serif;
- padding: 0.5em;
- }
- .name {
- color: black;
- background: white;
- font-family: "宋體";
- font-size: 30pt;
- padding-top: 0.2em;
- }
- </style>
- <div class="outer">
- <div class="boilerplate">
- 你好,歡迎來到
- </div>
- <div class="name">
- HTML 5在線
- </div>
- </div>
- </template>
現在,該Web組件在頁面上不可見,因為我們將它移到了一個template元素中,我們可以在JavaScript腳本代碼中訪問該Web組件。現在,我們將它放入nameComponent元素的shadow root節點中,代碼如下所示:
- <script>
- var shadow = document.querySelector('#nameComponent').webkitCreateShadowRoot();
- var template = document.querySelector('#nameComponentTemplate');
- shadow.appendChild(template.content);
- template.remove();
- </script>
現在,該組件將仍然被顯示在頁面上。如果你用鼠標右擊nameComponent元素并查看元素內容,你將只能看見如下所示的內容:
- <div id="nameComponent">HTML 5在線</div>
由此證明,通過Shadow DOM的使用,我們可以隱藏Web組件的展示細節,因為該細節被封裝在元素的shadow root節點中。
第二步:分離內容與展示
現在我們的Web組件的展示細節已經被隱藏起來了,但Web組件中的內容并沒有被獨立出來,因為盡管組件內容(“HTML 5在線”)被顯示在了頁面上,但是該內容是通過被復制在元素的shadow root節點中的方法顯示出來的。如果我們要修改組件內容,我們要再一次將其復制到元素的shadow root節點中。
在HTML 中,元素是可組合的,例如你可以在一個table元素中放入一個按鈕。此處我們要實現的就是這種組合:在背景色為紅色的容器元素中放入一個“HTML 5在線”文字。
你可以通過一個新的被稱為content的元素來自定義你的Web組件中的部分內容。該元素在Web組件中創建一個注入點,在JavaScript腳本代碼中可以向該注入點中動態注入內容。
接下來,我們首先修改template元素中的代碼如下所示:
- <template id="nameComponentTemplate">
- <style>
- ...
- </style>
- <div class="outer">
- <div class="boilerplate">
- 你好,歡迎來到
- </div>
- <div class="name">
- <content></content>
- </div>
- </div>
- </template>
現在我們的Web部件仍將被渲染在頁面上,但是原有“HTML 5在線”文字內容將被動態注入在content元素中。
如果你需要修改該文字內容,你可以使用如下所示的代碼:
- document.querySelector('#nameComponent').textContent = '陸凌牛';
現在我們已經實現了內容與展示的分離。內容被顯示在頁面中,而在Web組件內部實現內容的展示。
將內容與展示分離的好處
將內容與展示分離的好處在于:我們可以很輕松地實現對組件內容的控制。例如在上述示例中,如果需要修改“HTML 5在線”文字,我們只需修改shadow root節點中的內容(即textContent屬性值)即可,不需書寫其他任何代碼。
如果要修改組件中的其他任何內容或樣式,我們也只需要修改template元素中的樣式代碼或HTML代碼即可:
- <template id="nameComponentTemplate">
- <style>
- .outer {
- border: 2px solid brown;
- border-radius: 1em;
- background: red;
- font-size: 20pt;
- width: 12em;
- height: 7em;
- text-align: center;
- }
- .boilerplate {
- color: white;
- font-family: sans-serif;
- padding: 0.5em;
- }
- .littleFontSize{ font-size: 15pt; }
- .name {
- color: black;
- background: white;
- font-family: "宋體";
- font-size: 30pt;
- padding-top: 0.2em;
- }
- </style>
- <div class="outer">
- <div class="boilerplate">
- 你好,歡迎來到
- </div>
- <div class="name">
- <content></content>
- </div>
- <div class="boilerplate littleFontSize"> 國內首家在桌面瀏覽器中正式應用HTML 5技術的技術網站。 </div>
- </div>
- </template>
事實上,這個好處可以說是對目前Web技術的一個重大改善,因為你只需關注組件內部的實現代碼,而不需關注外部如何使用這個組件。 例如在上述示例中,我們可以在為中文頁面提供的組件中書寫“你好,歡迎來到”文字,而在為英文頁面提供的組件中書寫“Hello,welcome to”文字。
實現高級注入
在上面這個示例代碼中,可以動態向content元素中注入任何內容。事實上,我們可以使用多個content元素,并且通過select屬性定義每個content元素中所顯示內容的樣式。
例如,你可以定義一個Web組件,其中的內容如下所示:
- <div class='first'>示例文字1</div>
- <div class='second'>示例文字2</div>
- <div class='three'>示例文字3</div>
我們可以定義一個使用CSS樣式選擇器的shadow root節點,其代碼如下所示:
- <template id="nameComponentTemplate">
- <div style="background: purple; padding: 1em;">
- <div style="color: red;">
- <content select=".first"></content>
- </div>
- <div style="color: yellow;">
- <content select=".second"></content>
- </div>
- <div style="color: blue;">
- <content select=".three"></content>
- </div>
- </div>
- </template>
在這段代碼中,每一個div元素都與<content select="div">元素相匹配,<div class='first'>元素同時與<content select="first">元素相匹配,<div class='secong'>元素同時與<content select="second">元素相匹配,,<div class='three'>元素同時與<content select="three">元素相匹配。從運行結果中我們可以看出,<div class='three'>元素的背景色為紫色,文字為藍色,這是因為我們在組件內部定義所有div元素的背景色為紫色,且內容為< content select="first">的div元素的文字顏色為藍色的緣故。
本文小結
本文對Shadow DOM做一基礎介紹。你可以通過Shadow DOM實現更為復雜的處理。例如,你可以在一個shadow容器中實現多個shadow root節點,可以在shadow root節點中放置shadow容器(即在Web組件中嵌套使用Web組件)。在Web組件標準中,包含除Shadow DOM之外的更多內容。例如通過Custom Element(定制元素)的使用,你可以使用聲明的方式,而不是使用書寫腳本代碼的方式來創建組件。