更多 vs 更少 - 簡單比較
神奇的是,雖然在標簽裡面放那麼多類讓我非常不爽,可是人們愛哈利,因為他太特麼能說了。提倡的某些東西,比如說 OOCSS 和單一責任原則,從我自己創建的一系列日益復雜的網站來看,我可以說這確實值得對樣式行為進行解耦,不過直到最近我才找到一種讓我覺得滿意的方式來實現它。
我原先有做過一個 BEM 的版本,它強調了獨立高於重用 ‐ 每個新的塊默認是沒有樣式繼承的,允許組件獨立開發並且可以避免打亂頁面其它樣式的風險。不過代價就是碎片化(fragmentation) ‐ 忽然你會發現你有了 10 種不同的樣式鏈接,12 種不同的藍色,18 種差別細小的按鈕樣式等。妮可?沙利文,OOCSS 的作者,去年在墨爾本做了一個超贊的演示,講到了這個問題是有多普遍,以及怎麼解決它。
對我來說,我覺得可以接受的解決方案是,深入 CSS 的預處理機能,從而取得 BEM 的獨立性以及 OOCSS 的一致性。比如說,下面這樣的:
應該改成這樣:
我成功終結了充滿了占位符的文件,比如滿眼都是那些_typography.scss 和 _brand.sCSS,這不但讓我有能力控制碎片化,同時還能默認保持了樣式的對每個新組件的獨立性。所有的東西都挺好的,起碼有那麼一段時間是這樣。
修飾符: M 是怎樣破壞 BEM 的
只要你做關於 CSS 類的命名 & 維護方面的任何研究,你一定會要看到尼古拉斯.加拉格爾的傑作"關於 Html 的語義和前端架構"。其中一部分特別吸引我,他稱之為修飾符的 '單類模式' vs '多類模式'。簡單的說,你的 Html 會有兩個版本,看起來像這樣:
這通過兩個備選的 CSS 模式實現:
這兩個模式的差別在於 btn--large 是否只要它自己就足夠了,還是說它需要依賴類 btn 。單類模式說”可以”,它看起來更簡單而且可以避免有人忘記把 btn 包含進來的情況。而且它也不啰嗦,配合 SASS 的 @extend 方法,它對 CSS 來說不像一個負擔,只不過它有一個致命傷。
上下文重寫
我們假設你的所有按鈕都有背景色,除了你頂部導航欄那些沒有。在多類模式下,所有的按鈕,大的小的,圓的方的,等等之類,都會包含類 btn,所以你可以這樣寫:
而在單類模式中,我們不知道哪種按鈕會被重寫,所以你只好這樣:
很顯然,這不理想 - 追加一個按鈕變體也就意味著要檢查所有地方的按鈕樣式重寫以及添加一個新類。用屬性前綴選擇器 ^=,可以檢查你的屬性是否以特殊字符串開頭,比如:
這實現了單類模式下的簡單的上下文重寫,不過它還是太弱了,不是一個可以托付的選擇。最致命的是,如果另一個類在 btn--large 出現,而且前綴選擇器還沒匹配到它,那麼之後的所有的都完蛋了。而且,它還沒有顯式的方法來指定多個變種,比如說 btn--large--rounded。
我很欣賞這種創造性的方式,不過還是死路一條。好吧,到這裡我特麼操蛋了,吐,直到忽然某天我靈光一閃。
為什麼要用類?
不要在意我那麼直接這種小問題,不過給我個理由先,為什麼類是我們放樣式信息的唯一位置?Html生存守則如是說:
3.2.5.7 類屬性
屬性,如果要指定,就必須有一個用來標記該元素屬於不同類的值,該值用一組空格分隔符來表示。
而開發者在類屬性中怎樣使用該標記是沒有任何限制的,但是鼓勵開發者使用描述該內容的屬性性質的表達,而不是描述該內容所期待呈現何種結果的表達。
所以對吧,我們用類來描述"內容的屬性性質"是很有道理的,但是好像我們對類需求過度了吧。一個屬性就包括了所有的東西,從巨大的 BEM-風格 命名,比如說 primary-nav__sub-nav--current 到像 u-textTruncate 或者 left 或者 clearfix,到 JavaScript 用的比如說 JS-whatevs,然後我們花了巨多時間來解決命名沖突的問題,然後還希望他們有很好的可讀性。
通過約定 & 習慣這是可控的,而且還有像文章前面說到的哈利的那種技術也挺有用的,不過,有一個事實是,我們所有的操作都是基於一個全局命名空間(global namespace)的,不管有多少規約都無法改變的事實。這讓我們下面出場的 AM 就有那麼一點不同了。
在我們正式開始討論它之前,我們來復習一下 CSS 一個鮮為人知的特點。
歡迎 ~= 登場,神奇的選擇器
從 IE7 開始,浏覽器開始支持一種超厲害的 CSS 規則,叫做空格分隔屬性選擇器(space-separated attribute selector)。它可以匹配任何屬性值,通過空格分隔,就像它們是類一樣。下面兩行的 CSS 是等價的:
和 <div class='a b c'> 一樣,它不在意 a,b 和 c 的順序,也不在意是不是還有其它, ~= 選擇器也不在意。不過 ~= 不限於類(class)屬性,這就是這種全新技術的關鍵。
屬性模塊(Attribute Modules)
屬性模塊,或者叫 AM,它的核心是如何為你的樣式定義命名空間。讓我們從一個簡單的例子開始,一個網格,首先用類的方式描述:
好了,然後我們用屬性模塊方式來做。我們有兩個模塊,行(rows) 和 列(columns) 。行,現在還沒有變化,列有 12 列。
首先你肯定會注意到了它們的 am前綴(am-prefix)。這是 AM 核心的一部分,它確保了屬性模塊不會和現有屬性沖突。你也可以用任意你喜歡的 ‐ 我試過用 ui- ,CSS- 還有其它一些,不過這個例子裡面我們約定用 am-。如果 Html 校驗對你的或者你項目來說很重要,那你就選個 data-,意思也是一樣的。
第二個你會注意到應該就是那些值了,什麼 "1","4" 還是 "12" 的,看起來非常糟糕 的類名 ‐ 它們太普通所以有太高的沖突幾率了。不過因為我們已經定義了我們的命名空間,它只在我們規定的地方起作用,所以我們可以隨便用那些我麼你覺得最簡明最有意義的字符。
靈活的屬性
到目前為止,差別相當細微。不過因為每個模塊都有它自己的命名空間,讓我們拿我們的值做個不一樣的嘗試:
現在我們可以用命名來讓我們的作用域看起來更有意義了 ‐ 一個寬是 1/3 標記讓我們立刻能想起我們需要有 4 列,因為我們用的是一個 12-列 的網格。我們還為所有的列定義了一個默認樣式 ‐ 也就是屬性 column ,沒有值的列將會被視為全寬的列。此外,我們還可以把一些重復的邏輯 (事實上列是左對齊的)移到了這個屬性規則裡面。
格式化所有的屬性和值
這是這種方法的核心優點之一。存在一個基礎屬性,比如 am-Button,可以並且應該定義樣式。這個屬性的每個擴展值則應該繼承或者重寫這個基礎樣式。
在上面的網格例子中,我們正是這樣做的:標簽 am-Column="1/3" 匹配到了兩個屬性[am-Column] 和 [am-Column~="1/3"],因此結果就是基礎樣式 + 改變。它給我們提供了一種方式來實現 所有的列都是columns 而不需要用重復的類或者用 SASS 的 @extend方法。
BEM 的零類(zero-class)模式
回到我們的 BEM 中的單類模式 vs 多類模式上來,AM 給了我們一個零類模式選項。比如說上面的按鈕的例子,標記看起來是這樣的:
通過創建一個新的屬性模塊 am-Button,我們可以分離出適用於所有按鈕的樣式,比如那些大按鈕,還有那些圓角按鈕。我們不僅可以自由的組合這些變化(比如 am-Button='large rounded'),我們還可以針對這些屬性本身做任何的上下文覆蓋:
現在不管你選什麼樣式的按鈕,或者你選多少種樣式的按鈕都不是問題了,關鍵是,所有的按鈕都會適用選擇器[am-Button],這樣我們就知道我們的覆蓋是有效的了。