因專案功能的關係,接觸到了Shadow DOM,那麼就來跟大家介紹一下什麼叫做Shadow DOM。
在了解什麼是Shadow DOM前,我們首先先來認識一下什麼叫做DOM呢?
DOM 是 W3C 所制定的一個規範,獨立於平台與程式語言的標準。只要遵守 DOM 的規範實作,那就不受限於所使用的平台或語言開發,都可以透過 DOM 提供的 API 來操作 DOM 的內容、結構與樣式 。也因此我們可以很簡易的透過前端網頁程式 JavaScript 來對網內容做操控。而 document 物件是 「DOM tree」的根節點,所以當我們要存取 HTML 時,都從 document 物件開始。document 提供了網頁文件所需的通用函式可以呼叫使用。
DOM是Document Object Model的縮寫,我們稱之為「文件物件模型」。 當瀏覽器在載入一個網頁時,會把HTML(strings of text)內的各個標籤,包括文字、圖片、屬性等等都轉成資料模型 (objects/nodes),而這些物件最終會形成一個樹狀的結構化表示法,瀏覽器就是透過建立節點樹(即 DOM Tree )來保留 HTML 的層次結構。
舉例一個基本的 HTM L :
<html> <head> <title>example</title> </head> <body> <H1>Hello World</h1> </body> </html>
而上述 HTML 的 DOM Tree 可參考下圖。
身為一個程式設計師,我們 知道網頁前端設計三大元素是:HTML、CSS、JavaScript。在開發前端網頁時,我們可能有踩過這種雷(或是沒踩過也可能聽過):當要使用或存取 HTML id 或是 class 時,可能因無法確定或是不小心讓它與頁面上現有名稱造成衝突,而此微小錯誤可能因此延伸出難以察覺的大問題。像是我們常使用 CSS的!important(雖然這語法很好用),但是卻可能造成樣式選擇器(style selectors)失去控制,整個頁面被強制受到此樣式的設定影響。相信大家看到這裡,應該點頭如搗蒜。因此,不論前後端程式,將程式模組化封裝起來,使之能夠重覆使用或是減少干擾,在開發上都是很重要的。接著就是 Shadow DOM 出場的時機了。
在對DOM有基本認識後,我們回到正題,什麼叫做影子 D OM 呢?
Shadow DOM 是三個Web組件(Web Component )標準之一: HTML Templates, Shadow DOM 和 Custom Elements。(註:還有個HTML Imports 但功能過時,現在已不建議使用)Web組件的一個重要屬性是「封裝」:可以將標記結構( markup structure),樣式(style)和行為(behavior )隱藏起來,並 與頁面上的其他代碼隔離,也就是說不會去干擾頁面上的其他代碼,也可以不受到頁面上其他程式碼的影響,並讓整個程式碼更簡潔,乾淨。
聽不太懂??沒關係,直接看以下例子就知道什麼是 Shadow DOM 了。
以下2個 HTML 的<input>就是 Shadow DOM ! 你有想過為什麼我們只是簡單寫input並設定Type="range"後,瀏覽器呈現網頁時會有出現讓我們拖曳的滑軸呢? 或設定Type="date",就能出現日期選單?
這就 Shadow DOM 的神奇之處。看似越是簡單-越是不簡單。
其實我們早就有接觸並使用到這些 Shadow DOM 了 ,只是不知道像是<audio>,<video>,<iframe>,各種<input>等元件原來這些元件都是 Shadow DOM 。
<input type="range" min="0" max="100" step="1" value="50">
<input type="date">
但當我們檢視網頁原始碼時也看不出特別之處。若要讓 Shadow DOM 現出原形,需要開啟瀏覽器的show user agent shadow DOM設定。
開啟開發人員工具(Ctrl+Shirt+I) > 點選此齒輪設定圖示 > 在左邊選單Preferences找到Elements 裡的show user agent shadow DOM並將它勾選。
接著我們就能看到 Shadow DOM 的真面貌了。原來有這麼多的細節被 Shadow DOM 藏起來了~以下我們先注意到2個#shadow-root ,這分別是 <input type="range">& <input type="date">的Shadow DOM 開始 。
現在我們大概了解了什麼是 Shadow DOM 及 Shadow DOM 是屬於Web組件(Web Component )標準之一。由上面的範例,我們知道了一般情況下是看不到的 Shadow DOM 所隱藏的內容的,這也代表我們無法直接控制或是影響到 Shadow DOM 。
Shadow DOM 有以下幾點特性,並能為我們提供Web 開發中常見問題的解決方案:
因為上述幾個特性,WEB開發人員也可以自行創建類似<input>、<video>、<audio>等的Custom Elements來使用。
接著介紹 Shadow DOM 的技術詞彙:
(以下程式範例是使用Shadow DOM v1 語法)我們可以使用Element.attachShadow()方法將 Shadow DOM tree 附加到我們指定的元素上並返回它的Shadow root。呼叫該方法時,可以設定 mode:open或是closed:
let shadow = elementRef.attachShadow({mode: 'open'}); let shadow = elementRef.attachShadow({mode: 'closed'});
設定{mode: 'open'} ,代表我們可以用頁面的 JavaScript 來存取此 Shadow DOM 並對它做相關的操作,若是設成{mode: 'closed'},則不允許外部操控此 Shadow DOM ,在呼叫shadow.shadowRoot 時會取到 null ,像是我們也無法取得<input type="range">的shadowRoot。而在建立 Shadow DOM 時還有以下兩個限制:
以下範例建立2個div,並讓id="shadowDiv1"的div裡的<p>元素套用shadowrootClass,因此裡面的文字應該要顯示為紅色;而id="shadowDiv2"的div,採用附加Shadow DOM方式寫入的HTML內容,雖然也指定<p>元素套用shadowrootClass但是卻不會顯示紅色,而此可見 Shadow DOM 其隔融性。
<html lang="en"> <head> <meta charset="UTF-8"> <title>Shadow DOM</title> <style> .shadowrootClass { color: #f00; } </style> </head> <body> <div id="shadowDiv1"><p class="shadowrootClass">Hello, Shadow DOM world!</p></div> <div id="shadowDiv2"></div> <script> //選擇shadowDiv2當成shadow host var shadowHost = document.querySelector('#shadowDiv2'); //在此節點上附件shadow root var shadowRoot = shadowHost.attachShadow({mode: 'open'}); shadowRoot.innerHTML = '<p class="shadowrootClass">測試自建一個shadow DOM</p>'; </script> </body> </html>
設成{mode: 'open'},網頁呈現結果:
設成{mode: 'closed'},網頁呈現結果:因為設定為close,因此會有錯誤。
Shadow DOM 最初的API現稱為v0,當前的API稱為v1
在創建 Web Components 時,就可以Shadow DOM 搭配 Custom Element ,來隔離自己元素的HTML,CSS和JS。
<html lang="en"> <head> <meta charset="UTF-8"> <title>Shadow DOM</title> </head> <body> <div id="shadowDiv1"><p class="shadowrootClass">Hello, Shadow DOM world!</p></div> <div id="shadowDiv2"></div> <script> customElements.define('say-hello', class extends HTMLElement { connectedCallback() { const shadow = this.attachShadow({mode: 'open'}); shadow.innerHTML = `<p>Hello, ${this.getAttribute('name')}</p>`; } }); </script> <say-hello name="Claire"></say-hello> </body> </html>
並不是所有瀏覽器都有支援 Shadow DOM,我們可以看以下各家瀏覽器的支援程度: