神秘的 shadow-dom 淺析
說到 shadow-dom 可能很多人會很陌生。但是其實我們肯定碰到過,本文主要想簡單介紹下 shadow-dom
。下面直接進入正文。
shadow-dom 是什麽
顧名思義, shadow-dom
,直譯的話就是 影子dom
?我覺得可以理解為潛藏在黑暗中的 DOM 結構,也就是我們無法直接控制操縱的 DOM 結構。前端同學經常用開發者工具的話,查看 DOM 結構的時候,肯定看到過下面這樣的結構:
這裏的 #shadow-root
所包含的內容其實就是所謂的 shadow-dom
。
shadow-dom
其實是瀏覽器的一種能力,它允許在瀏覽器渲染文檔(document)的時候向其中的 Dom 結構中插入一棵 DOM 元素子樹,但是特殊的是,這棵子樹(shadow-dom)並不在主 DOM 樹中。
舉個栗子,也是最常見的例子, <video>
標簽,我們創建在頁面上創建一個空白的 video
標簽:
1 |
< video id=‘test‘></ video >
|
查看 DOM 結構如下:
雖然我們創建的是一個空標簽,但是在這個空標簽內部,存在一個 shadow-dom
,點開 shadow-dom
可以看到內有乾坤,大有內容。其實這內部的具體內容,就是 <video>
的具體實現。
shadow-dom 結構示意
再用一幅圖總結一下:
document
這個很好理解,就是我們的正常文檔 document 。
shadow host
對於一個內部有 shadow-dom
的元素而言,它必然需要一個宿主元素,對於上面的例子而言, <video>
標簽,就是 shadow-dom 的宿主元素。
shadow-root
通過 createShadowRoot
(下文會提及) 返回的文檔片段被稱為 shadow-root 。它和它的後代元素,都將對用戶隱藏,但是它們是實際存在的,在 chrome 中,我們可以通常審查元素去查看它們的具體 DOM 實現。
在 <video>
中,例如暫停,播放,音量控制,全屏按鈕,進度條等都是 shadow-root 的後代。它們工作時會顯示在屏幕上,但他們的 DOM 結構對用戶是不可見的。
contents
就是上述所說的 <video>
中各子組件的 DOM 的具體實現。
為什麽需要 shadow-dom
為什麽需要有這種結構呢?
Shadow-dom 是遊離在 DOM 樹之外的節點樹,但是他的創建基於普通 DOM 元素(非 document),並且創建後的 Shadow-dom 節點可以從界面上直觀的看到。更重要的是,Shadow-dom 具有良好的密封性。
這是瀏覽器提供的一種“封裝”功能,提供了一種強大的技術去隱藏一些實現細節。什麽意思呢?以 w3c 上的一個 <video>
例子為例,我們僅僅是填寫了一個空白的標簽,再加上 src
屬性裏填上視頻地址,就可以播放視頻了:
我們僅僅填寫了一行代碼,卻擁有比這行代碼更多的功能,譬如暫停,播放,音量控制,全屏按鈕,進度條等等。
這些功能具體的 DOM 實現,其實都在 shadow-dom
中:
瀏覽器的開發者們意識到作為前端開發者,引用一個 <video>
標簽的時候,每次還要寫入一大堆 DOM 去控制控件的表現和行為,既不簡潔也很困難。所以他們界定了這樣一個界限,界定了哪些是你可以訪問的,哪些實現細節是訪問不到的。
那些不希望我們訪問到的細節,則封裝在了 shadow-dom
中。然而,瀏覽器本身卻可以隨意跨越這個邊界。設置這樣一個邊界之後,瀏覽器的開發者們就可以在我們看不見的地方使用熟悉的web技術、同樣的HTML元素去創建更多的功能,而不是像我們一樣要在頁面上用div和span來堆砌這些元素。
如何控制 shadow-dom
既然是瀏覽器開發者有意隱藏起來的 DOM 結構,那麽我們是否可以控制內部的 DOM 結構呢?並非完全不可以,還是有一些方法使得我們可以控制 shadow-dom
內的一些表現。
使用偽元素控制 shadow-dom 樣式
這裏我們要使用到偽元素,通過偽元素,我們可以控制 shadow-dom
中 DOM 結構的樣式。
在 chrome 下,查看 shadow-dom
結構(如果無法看到shadow-dom需要手動打開),可以看到每個結點都加上了一個 pesudo 屬性:
有了這些屬性,我們可以通過偽元素的方式控制他們,譬如在一些場景下 video 標簽的控制條不會自動隱藏或自動顯示,可以通過偽元素指定默認顯隱方式:
如果你在 chrome 瀏覽器下閱讀本文,從上面的 codePen 可以看到,我使用偽元素修改了 video 控件條的底色為粉紅色 deeppink。
不幸的是,上面的控制方式只適用於 chrome 瀏覽器,雖然大部分現代瀏覽器已經支持 shadow-dom
,但是能夠審查 shadow-dom
內部 DOM 元素的只有 chrome
瀏覽器,其他瀏覽器仍會把這些細節隱藏。
使用 Javascript 創建一個 shadow-dom 元素
我們也可以通常 Javascript 創建 shadow-dom
,實現各類功能的封裝,主要通過:
1 2 3 4 |
HTMLElement.prototype.createShadowRoot =
HTMLElement.prototype.createShadowRoot ||
HTMLElement.prototype.webkitCreateShadowRoot ||
function () {};
|
看看下面這個例子,在chrome內核瀏覽器下,將創建一個簡單的 shadow-dom
,將我們的代碼放入一個template
中,再通過 importNode
插入到 shadow-dom
中:
如果你現在在 chrome 內核瀏覽器下訪問本文,那麽上述的 codePen 中你應該可以看到 createShadowDomByJs 這一行文字,打開審查元素,會看到 <p>
結構是隱藏在 shadow-dom
中的。
shadow-dom 兼容性
shadow-dom 的未來
本文是非常基本的一些關於 shadow-dom
的概念,只是它的冰山一角,沒有十分深入的去研究。
現行的組件都是開放式的,即最終生成的 HTML DOM 結構難以與組件外部的 DOM 進行有效結構區分,樣式容易互相混淆。Shadow-dom 的 封裝隱藏性
為我們提供了解決這些問題的方法。在 Web 組件化的規範中也可以看到 Shadow-dom 的身影,使用具有良好密封性的 Shadow-dom 開發下一代 Web 組件將會是一種趨勢。
更多資源及參考文章
如果你讀完本文後仍然意猶未盡,可以看看下面這些文章:
- Introduction to Shadow DOM
- A Guide to Web Components
- Shadow DOM 201
- [譯]什麽是Shadow Dom?
神秘的 shadow-dom 淺析