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