在定義了規(guī)范后,元素、屬性(attribute)及屬性(attribute)值的語義還要受到開發(fā)商共同選擇和實現(xiàn)的影響,這就導致了后期規(guī)范化的約定好的語義要進行修改(這是HTML設計的一個原則)。
關于語義
語義是研究符號和標志,以及他們所代表的事物之間的關系的。在語言學里,主要是研究符號所表達的意思(如單詞、短語或聲音)。Web前端開發(fā)中,語義主要涉及到了約定好的HTML元素、屬性(attribute)及屬性(attribute)值的意義(包括一些擴展,如微數據)。這些約定好的語義,通常是正式的規(guī)范,可以用來更好的理解一個網站的各方面信息。但是,在定義了規(guī)范后,元素、屬性(attribute)及屬性(attribute)值的語義還要受到開發(fā)商共同選擇和實現(xiàn)的影響,這就導致了后期規(guī)范化的約定好的語義要進行修改(這是HTML設計的一個原則)。
區(qū)分不同類型的HTML語義
編寫“具有語義的HTML”原則是現(xiàn)代、專業(yè)前端開發(fā)的一個基礎。大多數的語義和自然存在的或預期內容的多方面相關(例如,h1元素、lang屬性(attribute)、type屬性(attribute)的email值、微數據)。
然而,并非所有的語義需要根據內容衍生出來的。類名不能是“非語義”的。無論使用什么名字,他們都需要有意義、有目的。類名語義跟HTML語義不同。我們可以使用HTML元素,某些HTML屬性(attributes)、微數據等約定的“global”語義,他們的目的不會和那些網站、特定的應用程序的“l(fā)ocal”語義產生混淆。那些“l(fā)ocal”的語義是包含在屬性(attribute)值里的,如class屬性(attribute)。
盡管HTML5 specification section on classes再次假定“最佳實踐”是:
“鼓勵作者使用[類屬性(attribute)]值來描述實際內容的本質,而不是描述那些所需的內容?!?/P>
本身沒有理由這樣做的。實際上,在開發(fā)大型網站或應用程序時,這樣經常會是一個阻礙。
內容層語義本來服務于HTML元素和其他屬性(attribute)。
類名給機器或者訪問者提供很少或沒有用的語義信息,除非是商定好的名字(并且是機器可讀的)的一小部分——微格式。
使用類名主要的目的是為了使用CSS和javascript。如果你不需要在你的web文檔里添加表現(xiàn)和行為,那么在你的HTML里,就可能不需要類名。
類名應該傳達有用的信息給開發(fā)者。當你在閱讀一個DOM代碼段的時候,它有助于理解一個特定的類名是做什么的,特別是在多個開發(fā)者的團隊里,前端工作者并不是唯一開發(fā)HTML組件的。
看一個非常小的例子:
<div class="news">
<h2>News</h2>
[news content]
</div>
這里的類名“news”不會告訴你任何信息,從內容上看,它本身就不是很明顯。關于這個組件的總體結構沒有給你提供信息,它不能夠用于非“news”的內容。試著將你的類名語義和實際的內容緊密結合,會降低架構可擴展性的能力和被其他開發(fā)者使用的易用性。
內容獨立的類名
在一個設計里,另一種方法是根據重復結構和功能模式衍生類名語義。復用性最高的組件是那些內容獨立的類名。
我們不應該擔心層與層之間的連接是否清晰明確,應該擔心那些反映具體內容的類名。這樣做并不是意味著類名“無語義”,它僅僅意味著這些類名的語義不是根據內容衍生出來的。如果一些額外的HTML元素有助于創(chuàng)造更強大的、靈活的、可重用的組件,我們就不介意使用額外的HTML元素,這只是意味著你使用了更多的元素來標記內容。
前端架構
一個組件、模版、面向對象的架構的目的是能夠開發(fā)一些可重用的組件,這些組件包含了一系列不同的內容類型。類名語義在大型應用程序里的重要性在于它是由實用性衍生出來并且很好的服務于這個組件的主要目的-為開發(fā)人員提供有意義,靈活,可復用的表現(xiàn)或行為性的接口,以便使用。
可復用、可組合的組件
總的來說,可擴展的HTML或CSS必須依賴HTML里的類來創(chuàng)建可復用的組件。一個靈活的、可復用的組件既不是依賴于存在的DOM樹里的某個部分,也不需要使用特定的元素類型。它應該能夠適應不同的容器,并且可以容易的加上樣式。如果有必要,添加額外的HTML元素(除了那些僅用來標記內容的HTML元素)可以用來創(chuàng)建更強健的組件。一個很好的例子就是Nicole Sullivan所說的的media object.
易于組合的組件得益于使用選擇器類型,而避免使用類型選擇器。下面的例子降低了btn部分和uilist部分易組合性。問題在于.btn的優(yōu)先級比.uilist a(會覆蓋重復的屬性樣式)的優(yōu)先級低,并且uilist需要a作為子結點。
.btn { /* styles */ }
.uilist { /* styles */ }
.uilist a { /* styles */ }
<nav class="uilist">
<a href="#">Home</a>
<a href="#">About</a>
<a class="btn" href="#">Login</a>
</nav>
提高一個組件部分和uilist部分的易組合性的一個方法是使用類給子DOM元素添加樣式。這樣可以減少樣式規(guī)則的特性,但是最大的好處就是允許你將結構上的樣式應用到任何類型的子節(jié)點上。
.btn { /* styles */ }
.uilist { /* styles */ }
.uilist-item { /* styles */ }
<nav class="uilist">
<a class="uilist-item" href="#">Home</a>
<a class="uilist-item" href="#">About</a>
<span class="uilist-item">
<a class="btn" href="#">Login</a>
</span>
</nav>
Javascript特定類
使用javascript特定類有助于降低因組件主題樣式或結構改變而導致原來應用在上面的javascript不起作用的風險。我發(fā)現(xiàn)一個方法是使用某些只給javascript使用的類——js-*——不給這些特殊類添加任何樣式呈現(xiàn)。
<a href="/login" class="btn btn-primary js-login"></a>
改變組件的結構或主題將無意中影響任何所需的JavaScript行為和復雜的功能,使用這種方法,你可以減少這種無意中的情況發(fā)生。
組件修飾符
組件經常會有些變化,和基組件的呈現(xiàn)有稍微的不同,如不同顏色背景或者邊框。有兩種模式可以使用來達到這些變化。我把這兩種模式稱為“單類”模式和“多類”模式。
“單類”模式
.btn, .btn-primary { /* button template styles */ }
.btn-primary { /* styles specific to save button */ }
<button class="btn">Default</button>
<button class="btn-primary">Login</button>
“多類”模式
.btn { /* button template styles */ }
.btn-primary { /* styles specific to primary button */ }
<button class="btn">Default</button>
<button class="btn btn-primary">Login</button>
在使用“單類”模式時,如果你使用了預處理器,你可能使用Sass的@extend功能減少一些維護的工作。然而,即使有預處理器的幫助,我優(yōu)先選擇的是使用“多類”模式,在HTML里添加類修飾符。
我發(fā)現(xiàn)“多類”模式是一個更具有擴展性的模式。例如,使用基于btn的組件,進一步添加了5種按鈕類型和3種按鈕尺寸。使用“多類”模式,最終你需要9個類,通過混合匹配達到效果。但是使用“單類”模式你需要24個類。
如果需要,使用組件修飾符也比較容易根據上下文調整一個組件。比如你可能在另一個組件里對btn的外觀要做些細微的調整,可以這么做:
/* "multi-class" adjustment */
.thing .btn { /* adjustments */ }
/* "single-class" adjustment */
.thing .btn,
.thing .btn-primary,
.thing .btn-danger,
.thing .btn-etc { /* adjustments */ }
“多類”模式意味著你只需要一個單獨的內部組件選擇器去匹配任意類型的btn——在這個組件里被應用了btn的樣式元素?!皢晤悺蹦J揭馕吨阈枰獙懗鋈魏慰赡艿陌粹o類型,并且在任何時候改變了一個按鈕的外觀,你都需要調整這個選擇器。
結構化類名
當創(chuàng)建組件時——并添加了“主題“——一些類用于區(qū)分不同的組件,一些類用于組件修飾符,一些類和DOM結點關聯(lián),他們被包含在一個較大的抽象呈現(xiàn)組件里。
很難判斷btn(組件)、btn-primary(修飾符)、btn-group(組件)和btn-group-item(組件子對象)之間的關系,因為這些名字不能清楚的使人明白這些類的用途。沒有一致的模式。
在過去的一年里我一直在嘗試命名模式,這種模式能夠幫助我更快的理解DOM片段里結點間的關系,而不是試著來回的在HTML、CSS和JS文件之間查看, 拼湊出網站的架構。這種模式主要受BEM系統(tǒng)方法命名的影響,但是,被我改編成一種更容易閱讀的形式。
t-template-name t-template-name--modifier-name t-template-name__sub-object t-template-name__sub-object--modifier-name component-name component-name--modifier-name component-name__sub-object component-name__sub-object--modifier-name is-state-type js-action-name js-component-type
我把一些結構當做抽象的“模板”,其它則當作更清晰的組件(通常建立在“模板”上)。但是這種區(qū)分并非總是必要的。
這只不過是我目前發(fā)現(xiàn)的一個命名模式而已。它可以采取任何形式。但是這種模式的好處在于它消除了那些只依賴(單)連接符,或者下劃線,或者是駝峰格式的模糊的類名。
原始文件大小和HTTP壓縮
任何關于模塊化與可擴展的CSS的討論關心的是文件大小和“膨脹“。Nicole Sullivan的言論中經常會提到文件大小存儲(以及維護改進),并提到了像Facebook這樣的公司采用這種方法的經歷。進一步的,我想我會分享我在預處理輸出時的HTTP壓縮效果,以及大量使用HTML類的一些事情。
當Twitter 的Bootstrap剛面世時,我重寫了編譯好的CSS,以便更好地與手動操作的文件比較大小。在最小化兩個文件之后,手動操作的CSS文件比預處理程序輸出的小10%。但是當兩個文件都通過gzip壓縮后,預處理程序輸出的CSS文件比手動操作的小了5%。
這強調了比較HTTP壓縮后文件大小的重要性,因為減小文件大小并不能說明一切。它暗示了有經驗的CSS開發(fā)者在用預處理程序時不必太過關心編譯后的CSS中有一定程度的重復,因為經過HTTP壓縮后,文件將變得更小。通過預處理程序處理更易于維護的CSS代碼所帶來的好處,要勝過關注原始CSS和壓縮后輸出的CSS的美觀或文件大小。
在另一個實驗中,我從網站上弄了一個60KB的HTML文件(由很多可重用的組件組成),刪除了它的每一個class屬性(attribute)。這樣處理之后,文件減小到25KB。當原始文件與修改過的文件都通過gzip壓縮后,它們的大小分別變?yōu)?.6KB和6KB——只相差1.6KB。自由使用class所導致的實際文件大小的結果已經不值得再去強調了。
我如何學會停止擔憂的…
多年來,許多技術熟練的開發(fā)人員的經驗,已經改變了大型網站和應用程序的開發(fā)方式。盡管如此,對于個人來說,放棄“語義化的HTML”的觀念,意味著將由內容來決定類名(即使非要這么做,也只能作為最后的手段來使用),這通常需要你開發(fā)完一個大型的應用程序后,才能夠強烈地意識到這種做法不靠譜的一面。你必須準備好拋棄老觀念,尋找替代方案,甚至重新審視以前已被你摒棄的方法。
一旦你開始寫大型的網站和應用,你和其他人不僅必須去維護它,而且還得積極地迭代改進。你很快會意識到即使你盡了最大的努力,你的代碼仍然開始變得越來越難以維護。已經有一些人提出了他們自己的方法來解決這些問題:Nicole的博客和面向對象的CSS項目,Jonathan Snook的可擴展的模塊化結構CSS,以及Yandex開發(fā)的BEM方法。這些方法值得我們花時間去探索。
當你選擇了以減少編寫和編輯CSS的時間為目的的方式來編寫HTML和CSS時,那么如果你想改變它們的樣式,你就必須接受花更多的時間去改變HTML元素的class。無論是前端還是后端開發(fā)者 —— 任何人都可以重新排列預建的“樂高積木”,這將是相當實用的;事實證明,沒有人會CSS魔法。