過去的一年,圍繞是否使用新的HTML5語義元素的爭論已演變成如何使用新的HTML5語義元素。今年結束前(很多是在本季度結束前)所有主流瀏覽器都已正式聲明支持這些元素,所以是時候開始使用這些元素了。當然,在瀏覽器的世界里并不是所有的瀏覽器都支持HTML5,所以如何寫出向后兼容的代碼就成了一個需要回答的主要問題。

問題

最大的問題就是當使用這些新的語義元素時,那些不支持的瀏覽器如何處理這些元素。在一個頁面中使用HTML5元素主要有三種可能的結果。

  1. 標簽被當做一個錯誤,并且被完全忽略。構建DOM結構時就像這些標簽不存在一樣。
  2. 標簽被當做一個錯誤,并且生成一個作為占位符的DOM節點。就像代碼所表示的那樣構建DOM,但是標簽上沒有應用樣式(被視為一個內聯元素)。
  3. 標簽被當做HTML5的標簽,并且生成正確的DOM節點。就像代碼所表示的那樣構建DOM,并且標簽上也應用了合適的樣式(許多情況下,被視為一個塊級元素)。

下面來看一個具體的例子,考慮下面的代碼:

      
        
<div class="outer">
     <section>
         <h1>title</h1>
         <p>text</p>
     </section>
</div>
							
      
    

很多瀏覽器(比如Firefox3.6、Safari4)會這樣解析這段代碼: <div> 作為一個頂級元素, <div> 下有一個不認識的子元素( <section> ),雖然不認識但是 <section> 被當做一個內聯元素構建在DOM中。 <h1> <p> <section> 的兩個子元素。由于 <section> 在DOM結構中,所以可以在節點上應用樣式。這是第二種情況#2。

IE9以前的IE瀏覽器會這樣解析這段代碼: <div> 作為一個頂級元素,將 <section> 元素看做一個錯誤。 <section> 會被忽略, <h1> <p> 被解析時作為 <div> 的子元素。關閉標簽 </section> 同樣也被當做錯誤忽略。這樣解析的效果就等同于下面的代碼:

      <div class="outer">
     <h1>title</h1>
     <p>text</p>
</div>
						
    

由此可見,舊的IE瀏覽器處理未知元素的策略確實能將頁面“合理”的恢復,但是相比其他瀏覽器它構建了一個不同的DOM結構。由于在DOM結構上沒有未知的元素,所以你也就不能在 <section> 上應用樣式。這是一個種情況#1。

當然,那些支持HTML5的瀏覽器中,比如IE 9、Firefox 4、Safari 5,就能像HTML5規范中規定的那樣構建出正確的DOM結構并且能應用正確的默認樣式。

因此,最大的問題不僅是瀏覽器從相同的代碼中構建出了不同的DOM結構,同時還為相同的DOM結構應用了不同的樣式規則。

解決方案

如今,很多人想出了很多不同的解決方案以使HTML5元素能用于頁面上。每個方案都是解決某個或某幾個已經提及的特定問題,以實現瀏覽器的兼容。

JavaScript 墊片(JavaScript shims)

JavaScript shims主要目的是解決HTML5元素在舊IE下的樣式問題。在IE中有一個眾所周知的怪異行為:IE會忽略掉它不認識的的元素,除非這些元素是通過 document.createElement() 創建的。所以如果調用 document.createElement("section") ,那么瀏覽器就會生成 <section> 的DOM節點并且在其上應用樣式。

像htmlshim [1] 這樣的shims就是利用這個特性使HTML5元素能在IE中生成DOM節點從而使你能在其上應用樣式。Shims通常還會在HTML5塊級元素上應用 display:block ,從而使其能做到瀏覽器兼容。

我不喜歡這種方法,因為它違反了我構建web應用的主要原則之一:不應該依賴JavaScript進行布局。這樣做不僅僅關系到會給那些禁用JavaScript的用戶帶來糟糕的體驗,而且它還關系到構建可預見的、可維護的、有清晰分層的web app代碼庫。它確實能在所有瀏覽器中生成相同的DOM結構并且使你的JavaScript和CSS正確的工作,但在我看來還是弊大于利。

命名空間hack (NameSpace hack)

從來不缺少hacks,還有另一項技巧可以使IE認識那些未知元素。這項技巧第一次獲得廣泛關注是通過Elco Klingen的 文章, HTML5 elements in Internet Explorer without JavaScript [2] . 這項技巧包括聲明一個XML風格的命名空間,然后使用這些元素時加上命名空間前綴,像這樣:

      
        
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html5="http://www.w3.org/html5/">
 <body>
     <html5:section>
         <!-- content -->
     </html5:section>
 </body>
</html>
							
      
    

html5 前綴僅僅是個前綴名字而非官方要求,所以你把前綴設置為“foo”效果是一樣的。這樣使用前綴,Internet Explorer就會認識這些新元素,從而你就可以在它們上面應用樣式了。這個方法在其他瀏覽器中同樣能夠工作,所以你就能在瀏覽器中獲得相同的DOM和樣式。

缺點同樣鮮明:你必須在HTML文檔中使用XML風格的命名空間,同時也要以相同的風格使用CSS,像下面這樣:

      
        
html5\:section {
     display: block;
}
							
      
    

這并不是我喜歡的編碼風格。這是一個漂亮的解決方案,教會我一種不自然的元素應用方式。我不希望看到文件中滿是帶有命名空間的元素。

防彈技術 ("Bulletproof" technique)

我第一次接觸這項技術是在YUIConf2010上,Tantek Celik做了一個主題演講, HTML5: Right Here, Right Now [3] 。在那個演講中,Taktek建議在每個HTML5的塊級元素中內嵌一個 <div> ,在這個 <div> 上設置一個CSS類用以表示它所代表的HTML5元素。例如:

      
        
<section>
 	<div class="section">
    		<!-- content --> 
	</div>
</section>
							
      
    

這種方法的目的是使內容能在所有瀏覽器中保持正確的文檔流。在一個HTML5的塊級元素中嵌入一個塊級元素意味著會有三種情況:你要么有一個單獨的塊級元素(Internet Explorer < 9),要么是一個塊級元素包含在一個內聯元素中(Firefox3.,Safari,etc),或者是一個塊級元素包含在一個塊級元素中。無論哪種情況,默認的渲染都是一樣的。

Tantek提出了這種方法的一個例外情況,就是 <hgroup> <hgroup> 明確指出不允許非head元素作為其子元素。為此,他推薦將 <div> 放在外邊:

      
        
<div class="hgroup">
	<hgroup>    
		<!-- content --> 
	</hgroup>
</div>
							
      
    

關于樣式,Tantek推薦不要試圖去給HTML5元素本身應用樣式,而是要給作為代理的 <div> 應用樣式。所以不要這樣做:

      
        
section {
     color: blue;
}
							
      
    

而是這樣做:

      
        
.section {
     color: blue;
}
							
      
    

這樣做的理由是,以后它會很容易的自動轉換成一個參照這個模式的HTML5元素的標簽。我并不熱衷于他的這個建議,因為我通常不喜歡通過標簽名稱應用樣式。

這個方法的缺點是會在不同的瀏覽器中生成不同的DOM結構,所以你在寫JavaScript和CSS時就要小心了。例如,使用孩子選擇器(>)時經過HTML5的元素時,就不能做到在所有的瀏覽器中工作正確。同時,直接訪問 parentNode 也會在不同的瀏覽器中獲得不同的節點。這種情況在下面這樣的代碼中尤為明顯:

      
        
<div class="outer">
	<section>
		<div class="section main">
         		<!-- content -->
		</div>
	</section>
</div>
							
      
    

比如你有個選擇器 section > .main ,它在Internet Expl 8和就早的版本中就不會工作。

反轉防彈技術(Reverse bulletproof technique)

有另外一些文章,像Thierry Koblentz的 HTML elements and surrogate DIVs [4] . 展示了反轉Tantek的方法:將HTML5元素包含在 <div> 中。例如:

      
        
<div class="section">
	<section>
	     <!-- content -->
	</section>
<div>
							
      
    

唯一的不同之處就是HTML5元素所在的位置——所有的元素都在相同的位置。支持者喜歡這項技術是因為他的一致性(對所有元素來說工作方式是一樣的,包括 <hgroup> )。 值得指出的是,像Tantek的方法一樣,它同樣會有CSS選擇器適用性和JavaScript DOM訪問的問題。它的主要優點就是一致性。

我的方法(My approach)

在方法的選擇上我的主要目標是:這個方法要使我僅僅改變頁面上的HTML。這意味著對于CSS和JavaScript來說要0修改。為什么要做出這樣的決定呢?對于web應用(或者其他應用)來說,你的變化所涉及的層越多,引入bug的風險就越高。將變化限制在一層那么就一定程度上限制了bug的引入,而且如果一旦出現bug,你就可以只在一個領域去尋找底層問題的所在。例如,如果布局被破壞了,我就知道這是因為我增加了 <section> 元素,而不是其他的什么CSS和JavaScript問題。

在調研了這些技術后,我做了一些原型和測試,最終我還是回到了Tantek的方法上。在不需要修改CSS和JavaScript的前提下,這是使我現有的原型頁面正常工作的唯一方法。不過,我并沒有完全按照他的方法來做,而是做了些變化,我認為這些變化對這個方法有所改進。

第一點,我從不在代表HTML5元素的類上加任何樣式(所以在我的選擇器中沒有 .section )。我保持頁面中已有的 <div> 元素,為它使用語義類名作為應用樣式和JavaScript的鉤子。例如,下面的代碼:

      
        
<div class="content">
     <!-- content -->
</div> 
							
      
    

就變成了這樣:

      
        
<section>
	<div class="section content">
	     <!-- content -->
	</div>
</section>
							
      
    

有了這點變化,我仍然使用 .content 作為樣式和腳本的鉤子。這樣,我已有的JavaScript和CSS就不需要修改了。

第二點,與其將 <hgroup> 作為一種特殊情況,我選擇不去使用它。事實是,我在我現有的頁面中找不到使用這個元素的場景。

為了從中選出一個較優的方案,我花費了大量時間在防彈技術和反轉防彈技術的比較上。對于我來說,做出選擇的關鍵因素是反轉防彈技術需要我增加CSS以使它能正常的工作。在那些為HTML5元素生成DOM節點但是沒有應用默認樣式的瀏覽器中,將HTML5塊級元素嵌套在 <div> 中不止一次的打亂了我的布局,因為在這些老瀏覽器中這些HTML5塊級元素變成了內聯元素。我必須顯示的添加規則將它們變成塊級元素以使我的布局能夠工作,但是這樣的話就不符合我的初衷了:不要修改CSS。

證明(The proof)

在討論時我發現的一件令人無比沮喪的事就是人們太輕易就否定一種方法了,因為他們總能找出至少一個反例。我在本文中展示的每一種方法都不是完美的;每一種方法都不能適用于你遇到的所有情況。如果你提供給我一項技術,我敢保證肯定有人能找出一個它不能工作的情況。但是這并不能否定這項技術的價值,它僅僅是告訴你這項技術有它的局限性,你可以做出更好的選擇。

在我的調研中,我使用一些已經存在的頁面并使用“改進的防彈技術”修改它們。我將它們放入帶有簡單布局的頁面、帶有復雜布局的頁面、帶有JavaScript交互和不帶有JavaScript交互的頁面。每一種用例下,我唯一需要修改的就是HTML并且一切都能正確工作(不需要修改CSS和JavaScript)。那些關于子節點和父節點關系的警告呢?有意思的是我從來沒有碰到那些問題。

對于我來說比較容易的原因可能是我代碼寫的比較嚴苛。我認真的復查:

  • 不用標簽名和IDs應用樣式(只用類)
  • CSS選擇器盡可能一般化,選擇器類型盡可能少
  • JavaScript不依賴特定的DOM結構
  • 不用標簽名操作DOM

另一件有趣的事是我使用HTML5作為容器。這些元素其實僅僅是功能組(功能塊)之間的界限。你在邊界內使用樣式和腳本,而不是穿越這些邊界本身。由于在邊界內使用那些元素的JavaScript和CSS能夠良好的工作,所以我猜想那些編碼良好的站點也能夠很好的使用這個方法。

結論

我最后選擇的這項技巧是修改Tantek的防彈技術得到的,同時我也將他推薦給其他人。很明顯,這個名字有點用詞不當,因為它對CSS和JavaScript有一些副作用,但是在我的經驗中它確實是唯一允許我只修改頁面中的HTML而其他部分能繼續工作的方法。我肯定爭論還會繼續,不管是在各公司的內部還是互聯網上。我希望本文能對你做出決定提供幫助。

引用(References)

  1. html5shim
  2. HTML5 elements in Internet Explorer without JavaScript ? , by Elco Klingen
  3. HTML5: Right Here, Right Now ?, by Tantek ?elik (? Video ?,? Slides ?)
  4. HTML elements and surrogate DIVs ?, by Thierry Koblentz