什么是Shadow Dom?
如果你做過網(wǎng)站,那么很可能你已經(jīng)用過一些JavaScript類庫。既然如此,你可能會對這些不知名的類庫作者心存感激。
這些作者——web開發(fā)領(lǐng)域的勇士們——都面對著同樣的一個問題——封裝。他們會花大量的精力在面向?qū)ο蟮慕?jīng)典問題之一上面,即如何封裝自己的代碼,以便與類庫使用者的代碼分離。
除了SVG,現(xiàn)在的Web平臺只提供了一種原生的方法去隔離代碼塊,這并不優(yōu)雅。沒錯,我說的就是iframe。對大部分需要封裝的場景來說,frames太重而且限制太多。
如果我需要把每個自定義的按鈕都放到iframe里,你是什么感覺,會不會瘋掉?
所以,我們需要一些更好的東西。事實上,大部分的瀏覽器已經(jīng)變相地提供了一種強大技術(shù)去隱藏一些實現(xiàn)細節(jié)。這個技術(shù)就是所謂的“shadow DOM”。
我的名字是DOM,Shadow DOM
Shadow DOM是指瀏覽器的一種能力,它允許在文檔(document)渲染時插入一棵DOM元素子樹,但是這棵子樹不在主DOM樹中。看一個簡單的slider:
- <input id="foo" type="range"/>
把這段代碼放到webkit內(nèi)核的瀏覽器中,它會這樣顯示:
很簡單吧,這里有一個滑槽,還有一個滑塊可以沿滑槽滑動。
嗯。一切看起來都那么美好,喝杯咖啡先……等下等下,這里居然有一個可以在input元素中滑動的元素!為什么我不能通過JavaScript看到它?
- var slider = document.getElementsById("foo");
- console.log(slider.firstChild); // 返回 null
這是一種魔法么?
我的觀點來看,不是。這只是shadow DOM在起作用。你看,瀏覽器的開發(fā)者們已經(jīng)意識到了手工編寫這些DOM元素的表現(xiàn)和行為很困難而且很SB。所以,從一定程度上講,他們騙了我們,給了我們一個輸入框,但擁有比輸入框更多的功能。
他們?yōu)槟?mdash;—web開發(fā)者設(shè)定了一個邊界,界定了哪些是你可以訪問的,哪些實現(xiàn)細節(jié)是訪問不到的。然而,瀏覽器本身卻可以隨意跨越這個邊界。設(shè)置這 樣一個邊界之后,它們就可以在你看不見的地方使用熟悉的web技術(shù)、同樣的HTML元素去創(chuàng)建更多的功能,而不是像你一樣要在頁面上用div和span來 堆。
有一些很簡單,就像上面說的slider。而有一些卻相當(dāng)復(fù)雜。我們來看一下video元素,它有一些按鈕、進度條、hover態(tài)的音量控制,像這樣:
所有的這一切都只是HTML和CSS——但是是隱藏在shadow DOM子樹中的。
借用XXX的一首詩,“它是怎樣工作的?”為了直觀一些,我們假裝可以用JavaScript操作它。看這個簡單的頁面:
- <html>
- <head>
- <style> p { color: Green; } </style>
- </head>
- <body>
- <p>My Future is so bright</p>
- <div id="foo"></div>
- <script>
- var foo = document.getElementById('foo');
- // 注意:這里只是模擬,不是真實的API
- foo.shadow = document.createElement('p');
- foo.shadow.textContent = 'I gotta wear shades';
- </script>
- </body>
- </html>
我們獲得了一個這樣的DOM樹:
- <p>My Future is so bright</p>
- <div id="foo"></div>
但是它像是被這樣渲染出來的:
- <p>My Future is so bright</p>
- <div id="foo"> <!-- shadow subtree begins -->
- <p>I gotta wear shades</p>
- </div> <!-- shadow subtree ends -->
看起來是這樣:
注意一下,為什么渲染的句子的第二部分不是綠色的?這是因為文檔(document)中選擇器p不能獲取到shadown DOM。很酷對不對?!如果一個框架開發(fā)者被賦予這樣的能力會怎么樣?想象一下你只需要寫你的widget,而不用擔(dān)心被不知哪里蹦出來的選擇器愚弄…… 簡直令人陶醉。
事件的情況
為了保持自然,shadow DOM子樹中的事件可以在文檔(document)中被監(jiān)聽。比如,你點擊一下audio元素中的靜音按鈕,你可以在一個包裹它的div中監(jiān)聽到這個事件。
- <div onclick="alert('who dat?')">
- <audio controls src="test.wav"></audio>
- </div>
但是,如果你要確認事件的來源,會發(fā)現(xiàn)它是audio元素,而不是它內(nèi)部的按鈕。
- <div onclick="alert('fired by:' + event.target)">
- <audio controls src="test.wav"></audio>
- </div>
為什么這樣?因為當(dāng)事件穿過shadown DOM邊界的時候,會被重新設(shè)定target,以避免暴露shadow DOM子樹內(nèi)部結(jié)構(gòu)。用這種方式,你可以監(jiān)聽到從shadow DOM中產(chǎn)生的事件,而實現(xiàn)者也可以繼續(xù)隱藏細節(jié)。
通過CSS訪問(Reaching into)Shadow
另一個需要提到的技巧是怎樣通過CSS來訪問shadow DOM子樹。假設(shè)我想自定義我的slider。我想讓它有一些樣式,而不是系統(tǒng)原生的那樣,像這樣:
- input[type=range].custom {
- -webkit-appearance: none;
- background-color: Red;
- width: 200px;
- }
結(jié)果如下:
很好,但是我怎樣定義滑塊的樣式呢?我們已經(jīng)知道,常規(guī)的CSS選擇器并不能獲取到shadow DOM子樹。但事實上,這里有一些很方便的偽元素,可以取到shadow DOM子樹中的元素。例如,slider中的滑塊在webkit中可以這樣訪問:
- input[type=range].custom::-webkit-slider-thumb {
- -webkit-appearance: none;
- background-color: Green;
- opacity: 0.5;
- width: 10px;
- height: 40px;
- }
樣子如下:
很***對不對?想想看,你可以為shadow DOM子樹中的元素賦予樣式,而不需要真的訪問到這些元素。而這些shadow DOM的作者有了決定哪些部分可以被賦予樣式的權(quán)利。如果你是作者,在做一些UI widget toolkit的時候,難道不想有這樣的能力嗎?
帶有洞(hole)的Shadow DOM,無窮的想象力
講完了這些令人驚嘆的能力,我們想象一樣,如果給一個有shadown DOM子樹的元素插入子元素會怎樣?我們來實驗一下:
- // Create an element with a shadow DOM subtree.
- var input = document.body.appendChild(document.createElement('input'));
- // Add a child to it.
- var test = input.appendChild(document.createElement('p'));
- // .. with some text.
- test.textContent = 'Team Edward';
結(jié)果如下:
哇!歡迎來到twilight DOM的世界!它是文檔(document)的一部分,可以被遍歷到,但是不會渲染!它是不是很有用呢?不一定,但是如果你需要的話它確實就在那等你。
但是,如果我們真的有能力把元素的子元素放入shadow DOM子樹中會怎么樣?想象一下shadow DOM是一個模板,通過它的某個洞(hole)可以看到內(nèi)部的子元素:
- // 注意:這里只是模擬,不是真實的API
- var element = document.getElementById('element');
- // 創(chuàng)建shadow DOM子樹
- element.shadow = document.createElement('div');
- element.shadow.innerHTML = '<h1>Think of the Children</h1>' +
- '<div class="children">{{children-go-here}}</div>';
- // Now add some children.
- var test = element.appendChild(document.createElement('p'));
- test.textContent = 'I see the light!';
如果你去遍歷DOM,你會看到這個:
- <div id="element">
- <p>I see the light</p>
- </div>
但是像是這樣渲染出來的:
- <div id="element">
- <div> <!-- shadow tree begins -->
- <h1>Think of the Children</h1>
- <div class="children"> <!-- shadow tree hole begins -->
- <p>I see the light</p>
- </div> <!-- shadow tree hole ends -->
- </div> <!-- shadow tree ends -->
- </div>
當(dāng)你添加子元素的時候,從DOM樹中看像一個正常的子元素,但是渲染的時候,他們從“洞(hole)”中進到了shadow DOM子樹。
寫到這里,你應(yīng)該會承認,這真的很酷,也會問:
瀏覽器中什么時候才會有呢?
家庭作業(yè)
你認為聽完了這么多說教的內(nèi)容會沒有家庭作業(yè)?作為一個JavaScript類庫或者框架的開發(fā)者,嘗試者去想象一下你可以利用shadow DOM制作的跟之前不一樣的偉大的東西。然后想一下shadow DOM可以應(yīng)用到的一些特定的使用場景(加上真實的或者模擬的代碼)。
***,共享你想到的使用場景到public-webapps郵件列表。關(guān)于在web平臺中加入這種能力的討論正在進行,我們需要你的幫助。
如果你不是一個框架作者,你仍然可以參與進來,你可以給shadown DOM加油,也可以將這份快樂傳播到你最喜歡的社交網(wǎng)絡(luò)上,因為快樂就是我們工作的全部。
附:SVG和shadow DOM
差點忘了,至于你信不信,我反正信了,SVG確實已經(jīng)用到了shadow DOM,從一開始就是這樣。但是比較麻煩的是,SVG的shadow DOM非常……非常……水(shady),不不,不是這個詞,是另一個詞,以sh開頭,以y結(jié)尾。(注:對英文語境不是太熟悉,評論中有人提到是 shy。)對對,就是它!我可以繼續(xù)說,但是請相信我對SVG shadow DOM的評價?;蛘吣憧梢圆榭次臋n。
原文地址:http://www.toobug.net/article/what_is_shadow_dom.html