CSS語義思維
原文
前一陣子在項目組中講了一個關於CSS的Session,在講之前我曾收到了許多意見,大部分是希望能講講CSS實用性的技術,比如盒模型,CSS3之類的。乾貨人人都喜歡,因為看得見摸得著,拿來就有用,但我最後還是決定講一些」濕貨「。因為在Code Diff的時候我發現了許多樣式的問題不是由於不會寫CSS導致的,而是由於在錯誤的地方使用了寫在錯誤地方的樣式。
其實CSS很簡單,沒有計算沒有流程,只是一直描述,無論什麼複雜的效果,你只要Google一下就知道怎麼寫了,甚至可以直接copy。但CSS又很複雜,一個元素的表現會受到它旁邊的兄弟元素,也會受到內部的子元素影響,還會受到父元素影響,在這種多重影響下,一個元素的顯示邏輯會變得錯綜複雜。有沒有面對塌陷的塊級元素而束手無策?無論怎麼改它的屬性就是得不到自己想要的,但看看似乎一模一樣的示常式序卻安然無恙,是不是恨得咬牙切齒?我想這就是本文所要解決的主要問題,讓你學會如何優雅的寫CSS。
涇渭分明 - 明確書寫意圖和表現語義
其實我們只要稍微接觸過CSS,一定都學過盒模型,我假設你已經對margin、padding和border已經很熟悉。好了,我們經常會遇到一種情況,想讓父元素和子元素之間有一些空間,比如布局的時候,container和content之間的留白。一般來說有兩種方式,一種是給父元素加padding內邊距,另一種是給子元素加margin外邊距。現在想想,你是否慎重考慮過用哪一種方式?你是否明白兩者的區別?如果你從未考慮過這些,現在考慮也不遲,這真的很重要,如果你真的想優雅的運用CSS,而不是被它耍的團團轉,如果你真的想把CSS寫出規模,那你就要認真的思考這裡面的邏輯。
先說說父元素的container加上padding的方式,子元素的背景將不會延伸到空白區域,另一種給子元素加margin的方式則反之。但我想先拋下這個表象,看看此時加上padding的父元素的邏輯。它意味著,我創建了一個容器,並且該容器的內部有一些區域是被看不見的東西填充的,無論放入什麼都不應該佔據這個區域。在你的腦海中想像一個厚厚的玻璃瓶,它就好像是在說「雖然我是透明的,但是在這厚厚的玻璃之內才是你應該呆的地方,無論是什麼只要你想放在瓶子里」。現在來看看子元素,是的,其背景也應該並且確實在內部,而不該佔據那段空白的區域。如果是為子元素加上margin,這意味著我在容器中創建了某個東西,而這個東西有一個自己的地盤,容器內的其他東西都不能進入這裡。
所以他們的本質區別在於其所表達的意義,也就是語義,一個是容器上的語義,另一個是容器內元素的語義,主語也不同,謂語意義也不同。那麼這能說明什麼呢,不得不提一下高級語言中的高級,這代表某個語言將機器語言封裝的更好,介面更接近自然語言,其強大不言而喻。其實編程語言的發展軌跡就是不斷把機器語言往自然語言翻譯,讓人們可以更容易的跟機器溝通,終極目標自然是人工智慧,和機器用自然語言自由溝通。所以雖然代碼或者說CSS的語法並沒有什麼變化,但我們應該在思維上清楚的區分代碼片段的意圖和語義來寫CSS。
語言學家喬姆斯基曾構造過一句符合語法的話:
「Colorless green ideas sleep furiously」
意思是無色的綠色想法憤怒的睡覺。想法睡覺是違反常識的,無色的綠色是矛盾的,憤怒的睡覺是不合常理的。僅僅符合語法是不夠的,僅僅不報錯也是不夠的,尤其是CSS這種描述性的語言很少會報錯,所以我想說的是,其描述性質讓我們有更多選擇來實現我們所期望的效果,但我們卻應該慎重,要讓語義分明,而不是在有限的語法規則下任意妄為,那樣寫不出好的CSS代碼。除了其本身的語法規則,我們必須自我約束,引入語義規則,不僅僅是為了提高可讀性這樣的理由,而是拉近人與機器的距離,讓我們在思維上更加和代碼契合,這樣才能寫出優秀的程序。
言歸正傳,究竟如何語義化CSS呢。之前有講過OOCSS,其實面向對象的思想就是一種語義化,將虛擬的元素看作實際的對象,用常識來構造代碼,就像是用戶體驗中的用戶習慣一樣,這是一種遍及全人類的用戶習慣,讓代碼更友好,即所謂的優雅。
比如最常見的布局,我們一般都會有header、body和footer,這樣無需任何說明和規則,誰都會理解,header在前,footer在後,中間是body。有趣的是table中的thead、tbody和tfoot,由於載入優化,我們即使把tfoot放在tbody前面,讓其先載入,但顯示的時候tfoot仍然會在tbody下面,由此可見語義化的重要,否則你一定會以為不是W3C的人弄錯了標準,就是瀏覽器廠商搞出了Bug。雖然這是HTML標籤不是CSS,但我覺得這個例子很恰當的說明了一點,顯示邏輯是獨立的,不應該和其他邏輯混為一談,它應該只關心如何顯示。
再比如,一個導航條一般分為左右兩邊,裡面各有若干鏈接。你會如何命名?navbar-left和navbar-right是Bootstrap所使用的class,但當寬度減少,Responsive響應式的Bootstrap會讓這些鏈接都收進下拉菜單中,左和右又從何談起呢?同樣是Bootstrap中的顏色命名就做的十分成熟,不是green、blue、red這些辭彙,而是success、primary、warning這些詞,它們的區別是表現化與語義化的區別,我喜歡把這個叫做`顯示邏輯`。也就是說在class層面我們應該只關注元素(或者說對象)是用來做什麼的(意圖)以及它應該表示什麼(語義)。這裡三種顏色代表成功、重要和警告,至於我們是用綠、藍和紅來表示還是通過其他什麼顏色甚至形狀,應該在style層面寫CSS屬性表示。如此我們使用class時無需迷茫於表示成功應該用綠色還是紅色,意圖明確無誤。回到導航欄的例子,我們應該使用main和sub之類的詞語標識導航欄中重要的和次要的鏈接,即使在某些實現下,主要和次要沒有區別也不用擔心,那是該實現的顯示邏輯,我們不應該橫加干涉。
狡兔三窟 - 善用class與style多對多的複雜關係
說到class和style(樣式屬性),這又是一段混亂不清的關係。其實每當我們要實現一個樣式,自覺或不自覺的都會考慮,是將元素和class一一對應,然後為元素上的class寫style,還是用class和幾條style對應之後在元素上搭配組合。前者的好處是每個class中的style不會影響其他class,但可用性十分的低,極端點說這已經不算是在編程了,而是在畫頁面;後者的好處可以靈活使用不同的樣式搭配給元素使用,組合出元素需要的效果,但很難維護,牽一髮動全身,樣式很難測試,無法保證在你動了一條style之後,沒有影響其他的地方。甚至最要命的是大部分CSS代碼都是無意識的遊走在這兩種情況之間,所以我們要做的不是選擇一種極端,而是找到一個平衡點,並使用一些方法使得我們的CSS既能靈活復用在各種元素上,又能易於維護,不致修了這破壞那的情況。
分組
首先,對於一個或一套元素的樣式,我們應該有自己的創建原則,而不是想到哪寫到哪。比如,我一般使用這樣的步驟來創建元素:
1. 結構
首先僅關注結構布局,以站點整體為基準,將元素抽象為一個或多個結構。
比如製作一個按鈕,你也許會發現它的尺寸和導航上的鏈接一致,而內外邊距以及display和overflow之類的屬性又和頁面容器一樣,這時你就可以把按鈕的結構拆成兩個結構,一個是尺寸,一個是邊界邏輯,這種情況時常發生,所以有的CSS理論認為應該把width和height這對尺寸屬性和padding以及margin這對邊距屬性徹底的分開也是這個道理。其實並不是說它們4個放一起就決定不行,只是一般它們都有各自復用的價值,所以常常被拆開使用,歸根揭底這是由顯示邏輯決定的。
2. 皮膚
一般由於設計統一性,我們可以藉助設計指導,輕鬆的製作出標準皮膚。但要注意的是,皮膚指的不僅僅甚至不一定是顏色之類的。繼續用Bootstrap舉例,有5種基礎顏色,但這並不是皮膚,5種顏色的語義不同,它們只是默認用了5種顏色,勉強可以看作默認皮膚,而真正的皮膚是theme,讓按鈕變得有立體感。還有一個誤區就是認為哪幾種屬性是皮膚,哪幾種不是。就像前面所說的,我們要在語義上進行劃分。一個屬性在某些意圖下可能是結構,在另外的意圖中可能是皮膚。舉個極端的例子,在一個Workshop中我用CSS和HTML製作了左邊這樣的Logo,Logo本身應該完全算是皮膚,因為一般來說就是一張圖片,所以製作它的所有CSS屬性都應該算作皮膚。下面的CSS代碼中,無論是定位還是文字的處理,以及尺寸等,都是為了構造Logo,所以這些屬性都是皮膚。
#logo i, #logo b {n position: relative;n}n#logo i {n left: -30px;n transform: rotate(-30deg);n font-size: 60px;n letter-spacing: -11px;n opacity: 0.3;n}n#logo b {n top: -43px;n left: 40px;n font-size: 68px;n word-break: break-all;n width: 3px;n line-height: 10px;n text-indent: 6px;n}n
3. 狀態
除了結構和皮膚,其實還有一類很容易被人忽視的樣式,即使是設計人員也經常忘了它,那就是狀態。最常見的是hover,滑鼠觸碰元素與否的狀態,以及active,當前選中的標籤,當前所在的分頁等等。通常人們會把它劃分到皮膚中,但你要明白我們劃分的依據是意圖,很顯然,狀態不是皮膚,僅僅是通常為表達某些狀態時會使用一些顏色,畢竟顏色是最好的表示方法。它還與各種邏輯有著千絲萬縷的聯繫,經常會使用JavaScript來加以控制,所以將狀態單獨分出來是極有必要的,JavaScript只需要更改一個class就可以實現狀態的切換,而不是在執行的時候才想起來應該把哪裡隱藏把哪裡顯示,或者是變個顏色出個動畫之類的。
使用
接著,無論我們是已經把各種寫好的CSS屬性分好了組,還是正準備以這種方式開始寫CSS,我先引入一個語言學的術語 `語塊(Lexical chunk)`,創造這一術語的 Michael Lewis 認為語言並非由傳統語法和辭彙組成,而是由多個辭彙的預製語塊組成。我們現在分好的組其實就是一個個CSS的語塊,CSS本身就是這種預製功能,我們要做的就是把style語塊化,然後在HTML中寫上代表它的class名稱,當HTML元素上的多個class組合在一起時就組成完整的語義。
比如表示步驟的元素,這是一個形狀類似標籤,一個挨一個的排列的元素。我將它的尺寸等屬性抽出來命名為label,然後把布局的屬性組命名為float-left,如果我的設計風格是扁平化的,我可以把相關的皮膚屬性命名為flat,你知道雖然扁平化一般用不著特別的屬性,但我也可以寫一些強制去掉圓角和漸變背景的屬性。作為步驟,會分為當前的步驟,之前的步驟,以及還沒到的步驟,當前的步驟可以命名為current,或者is—active,後面的步驟不能點所以可以命名為is-disable,這些都是狀態。而現在我們來看看當前步驟元素是什麼樣的
<div class="flat label float-left is-active"></div>n
總結成一句話就是「The flat label which float left is active」。這就是語義化的CSS。把組織好的語塊像說話一樣作用於元素上,對元素髮出指令,讓其變成想要的樣式。而且你應該會發現,一組該元素所獨有的屬性,或者是尺寸之類的一般是主語,布局是動賓短語,而皮膚多是一些形容詞,最後狀態可以用表語。
總之,CSS作為一個描述性的語言,有很多人覺得不能算一種語言,但我反而覺得這是一種高級語言,因為更接近自然語言。當然其主要作用是在視覺渲染上,所以應該是一種受限的語言,可以看作是接近自然語言的子集。所以它的性質覺得了其重語義輕語法的特點,寫CSS不能僅靠語法規則,一定要用上語義規則,最好是能和項目中所有人達成共識,CSS框架其實就是一種通用共識。
推薦閱讀: