
前言
什么是 helper ?任何框架都不是萬能的,而業務需求卻是多種多樣,很多時候我們只需要更改組件的部分屬性,而 helper 就是調整細節的工具。我在之前的文章《如何編寫輕量級 CSS 框架》中也舉過例子,我們完全沒必要因為幾個屬性的不同而重新編寫新組件。大部分的 helper 都是一個類對應一個 CSS 屬性,屬於最細小的類。通過工作的實踐總結,我覺得編寫一套簡單易用、通俗易懂的 helper 非常重要。本文的目的就是探討 helper 的組成部分、編寫方式以及如何精簡 helper 的命名。
組件與零件
詳細介紹如何編寫 helper 之前,先說一下我對於組件以及零件的看法。在之前編寫輕量級 CSS 框架的時候,我們是以組件的方式開發。而編寫 helper 更像是開發一個零件,因為 helper 的屬性單一,而且多個 helper 可以形成一個組件。比如下面的例子:
假設有 .boxes 組件
.boxes { border: 1px solid #eee; border-radius: 5px; margin-bottom: 15px; overflow: hidden; }
假設有如下 helper
.b-1 { border: 1px solid #eee !important; } .r-5{ border-radius: 5px !important; } .m-b-15{ margin-bottom: 15px !important; } .overflow-hidden { overflow: hidden !important; }
則 .boxes = .b-1 + .r-5 + .m-b-15 + .overflow-hidden
我是一個模型愛好者,這樣的組合方式讓我想到了壽屋的 HEXA GEAR 系列模型,這個系列的特點是“零件+零件=組件、組件+組件=骨架、骨架+骨架=素體、素體+武裝=機體”。
在編寫 helper 的時候,基於以上想法,我在思考是否可以把 helper 拆分的足夠精細,這樣它就可以自成一體形成一個框架,也就是“零件+零件=組件、組件+組件=框架”。令人遺憾的是,我的想法已經被人實踐,前幾天瀏覽 GitHub 時發現了相關的項目 tailwindcss,這個框架就是以 helper 為基礎,通過屬性疊加的方式添加樣式。
組件式框架和零件式框架是兩種完全不同的思想,難分伯仲,各有優缺點。
Helper 的組成部分
一套完整的 helper 應該包含哪些內容呢?一般常用的有 padding、margin、font-size、font-weight 等。為了編寫更為通用的 helper,我們需要更細致的划分。雖然我們並沒有打算把它寫成一個框架,但是我們希望 helper 的功能足夠強大。通過對比和思考,我將 helper 暫時划分成以下幾個模塊:
- Colors(顏色,包括 bg-color 及 text-color)
 - Paddings(內邊距序列)
 - Margins(外邊距序列)
 - Typography(排版,包括 font-size 及 font-weight)
 - Border(邊框線)
 - Radius(圓角)
 - Shadow(陰影)
 - Size(尺寸,包括 height 及 width)
 - Gutters(柵格間距序列)
 - Alignment(主要是 vertical-align)
 - ...
 
和之前編寫輕量級框架一樣,我們同樣使用 Sass 預編譯器。helper 類幾乎都是 Sass 循環生成的,所以源代碼看上去很精簡。
顏色變量
因為顏色稍微特殊一點,我將顏色與其它內容分開單獨介紹。在編寫輕量級框架的時候,我也定義了常用的一些顏色,但是面對特殊需求時略顯單一,所以我們需要使用 helper 擴充顏色集群。但是顏色是一個無法量化的概念,所以再強大的 helper 也無法面面俱到,只能是一定程度上的補充。參考常用的顏色值,最終我設置了紅、橙、黃、綠、青、藍、靛、紫、粉、冷灰、暖灰等幾種色系。

其中每個顏色都有六個亮度值,分別用 -lightest、-lighter、-light、-dark、-darker、-darkest 表示,此處有參考 tailwindcss 的顏色命名。這些顏色都是通過 Sass 的顏色函數生成的。以灰色為例,Sass 代碼如下:
$gray:#999;
$gray-light:lighten($gray, 15%);
$gray-lighter:lighten($gray, 25%);
$gray-lightest:lighten($gray, 35%);
$gray-dark:darken($gray, 15%);
$gray-darker:darken($gray, 25%);
$gray-darkest:darken($gray, 35%); 
        這些顏色序列看上去很像一套馬克筆,不過馬克筆灰色系更豐富,包括冷灰、暖灰、藍灰、綠灰。
其中背景色的循環方式如下,為了便於循環,我們定義了一個 color map,然后用 @each 方法循環。
$color-list:( 'gray':$gray, 'brown':$brown, 'red':$red, 'orange':$orange, 'yellow':$yellow, 'green':$green, 'teal':$teal, 'blue':$blue, 'indigo':$indigo, 'purple':$purple, 'pink':$pink ); @each $name,$color in $color-list { .bg-#{$name} { background-color: $color; } .bg-#{$name}-light { background-color: lighten($color, 15%); } .bg-#{$name}-lighter { background-color: lighten($color, 25%); } .bg-#{$name}-lightest { background-color: lighten($color, 35%); } .bg-#{$name}-dark { background-color: darken($color, 15%); } .bg-#{$name}-darker { background-color: darken($color, 25%); } .bg-#{$name}-darkest { background-color: darken($color, 35%); } }
命名策略
理所當然,我又提到了命名策略。在編寫輕量級框架的時候,我也着重討論了類命名策略以及比較了一些框架的命名方式。無論是框架還是 helper,類命名都決定了其易用性,而且會影響使用者的習慣,所以我會從簡潔、直觀、易用等幾個角度命名。不過 helper 的命名比較簡單,因為幾乎大多數都是單一的 CSS 樣式,所以命名策略基本都是對 CSS 屬性的抽象與簡化。
數字型命名 VS. 尺寸型命名
我在工作中接觸過兩種 helper 序列的表示方法,一種是常見的數字型,另一種是尺寸型。以 padding 為例:
數字型
.p-5 { padding: 5px !important; } .p-10 { padding: 10px !important; } .p-15 { padding: 15px !important; } .p-20 { padding: 20px !important; } .p-25 { padding: 25px !important; }
尺寸型
.p-xs { padding: 5px !important; } .p-sm { padding: 10px !important; } .p-md { padding: 15px !important; } .p-lg { padding: 20px !important; } .p-xl { padding: 25px !important; }
雖然在實際應用時,尺寸型寫法並沒有什么不妥,但很明顯它的擴展性很差,而且不直觀。作為例子,我只寫了五個數值,但如果我們希望添加更多的 padding 值的話,尺寸型命名就乏力了。我認為,凡是可以量化的屬性,比如 padding、margin、font-size、border-width 等,應該直接用數值表示,而對於不可以量化的屬性,比如 box-shadow,用尺寸型命名比較合適。
精簡命名
大多數的 helpr 命名都是 CSS 屬性的首字母縮寫形式。比如 p 表示 padding、m 表示 margin、f-s 表示 font-size 等。這符合我們期望的簡潔直觀的要求。但也不能唯縮寫論,所有的命名都用縮寫,因為有些屬性的縮寫會重復,而且有些縮寫之后就不知道具體含義了。我們可以沿用之前的規則,可以量化的屬性都用縮寫,不可以量化的屬性用簡化的全稱(比如 box-shadow 可以替換為 shadow)。
以 padding 循環為例:
@for $counter from 0 through 6 { .p-#{ $counter * 5 } { padding: ($counter * 5px) !important; } .p-t-#{ $counter * 5 } { padding-top: ($counter * 5px) !important; } .p-r-#{ $counter * 5 } { padding-right: ($counter * 5px) !important; } .p-b-#{ $counter * 5 } { padding-bottom: ($counter * 5px) !important; } .p-l-#{ $counter * 5 } { padding-left: ($counter * 5px) !important; } }
對於其它幾個 helper 與此類似,循環也很簡單。
關於 Margin 負值
margin 的 helper 相比其它來說比較特殊,因為它有負值,所以我們必須考慮如何表示負值。有些框架用 n (negtive)表示負值。比如 m-{t,r,b,l}-n-* 的形式:
.m-t-n-5 { margin-top: -5px !important; } .m-r-n-5 { margin-right: -5px !important; } .m-b-n-5 { margin-bottom: -5px !important; } .m-l-n-5 { margin-left: -5px !important; }
我覺得完全可以簡化一步,用 - 表示負值,簡單易懂,如下:
.m-t--5 { margin-top: -5px !important; } .m-r--5 { margin-right: -5px !important; } .m-b--5 { margin-bottom: -5px !important; } .m-l--5 { margin-left: -5px !important; }
雖然這種命名方式很簡潔,但看上去和其它 helper 不太統一。
關於圓角
圓角的 CSS 屬性名為 border-radius,如果直接簡寫的話和 border-right 就重復了,參見其它框架的表示方法有 corner-rounded、rounded 等。我們也可以簡化一下,比如直接用 r 表示,既可以代表  rounded 也可以代表 radius,一舉兩得。這樣的表示方法應該不會有歧義,畢竟在我們的腦海中,r 表示半徑算是一個根深蒂固的概念。Sass 代碼如下:
@for $counter from 0 through 10 { .r-#{ $counter } { border-radius: ($counter * 1px) !important; } .r-t-l-#{ $counter } { border-top-left-radius: ($counter * 1px) !important; } .r-t-r-#{ $counter } { border-top-right-radius: ($counter * 1px) !important; } .r-b-r-#{ $counter } { border-bottom-right-radius: ($counter * 1px) !important; } .r-b-l-#{ $counter } { border-bottom-left-radius: ($counter * 1px) !important; } }
我們用 -full 表示 100%,其它框架也基本如此,稍后再談論 r-100% 這種形式的可行性及問題所在。
.r-full { border-radius: 100% } .r-t-l-full { border-top-left-radius: 100% } .r-t-r-full { border-top-right-radius: 100% } .r-b-r-full { border-bottom-right-radius: 100% } .r-b-l-full { border-bottom-left-radius: 100% }
同樣的,高度和寬度的 100% 數值也用 -full 表示,循環方式類似。
關於陰影
我們在之前反復提到了陰影屬於非量化的屬性,所以只能使用尺寸型命名法,當然用數字也不是不可以,一會兒再詳細說明。先看源代碼:
.shadow-xs{ box-shadow:0 1px 5px 1px rgba(0,0,0,.15); } .shadow-sm{ box-shadow:0 2px 10px 2px rgba(0,0,0,.15); } .shadow-md{ box-shadow:0 3px 20px 3px rgba(0,0,0,.15); } .shadow-lg{ box-shadow:0 4px 30px 4px rgba(0,0,0,.15); } .shadow-xl{ box-shadow:0 5px 40px 5px rgba(0,0,0,.15); }
整體而言,比較簡潔,不過陰影的數值我是粗略添加的,實際情況要做調整。說點題外話,我個人覺得對於非量化的屬性本身而言,或許用處就不大,因為這些屬性能夠滿足業務需求的可能微乎其微,但是它仍然是不可缺少的一部分。所以說“通用的” helper 並不一定通用。
關於強度表示法
通過 font-weight 說一下關於強度的表示法,font-weight 的 CSS 屬性本身就有兩種表示法,一種是直接文字命名,比如 .f-s-thin , .f-s-normal, .f-s-bold 等,另一種是比較直接的 100 ~ 900 數值型表示法。以我個人觀點,我更傾向於數值型表示法,簡單直觀,並沒有歧義,也算是約定俗成的規定吧。font-weight 的循環比較簡單,而且數值有限,我們可以直接寫出從 100 ~ 900 的所有 helper。其它類似的 helper  也可以用 100 ~ 900 表示強度,比如顏色。
需要注意的是,編寫 helper 時一定要對數值型、尺寸型、強度型命名做好歸類與統一,切記毫無章法地胡亂使用。
類命名中的特殊字符
對於 r-100% 或者 w-100% 這樣的寫法是可以的,但是在定義 CSS 時要進行字符轉義,比如
.r-100\% { border-radius: 100% }
使用方式如下
<div class="r-100%"></div> 
        但是這種寫法總給人怪怪的感覺,而且輸入時要按 shift + %,不太方便,所以暫時只作為參考。
另外需要說明一點,我們可以通過特殊字符定義百分數,比如:
.w-50 { width: 50px; } .w\:50 { width: 50% }
通過約定的這種規則,我們就可以為 helper 添加柵格系統了。不過這只是暫時的想法,畢竟我們已經有一套輕量級 CSS 框架了。
序列數量
因為 helper 是循環生成的,所以循環的數量決定了 helper 的豐富度。那么循環的數量多少合適呢?這是所有 helper 最難統一的地方。不可否認,helper 的數量越多,通用性越強,也就越靈活。任何事物都有兩面性,雖然 helper 越多越好,但是數量太多會造成文件臃腫。目前我寫的 helper 的文件體積幾乎和之前的輕量級框架差不多,某種程度上來說確實在向“零件化”的框架發展。另一方面,其實 helper 並沒有必要寫的太全面,很多數值存在冗余。
簡單來說,對於有限值的 helper 就可以全部寫出,比如對其方式、font-weight 等。而對於任意數值的 helper 來說,我們需要選擇常用的一些數值,比如 padding、margin 等屬性,基本 1~50 px 之間就可以了,而圓角 1~20 px 足矣。不能量化的屬性比如陰影就完全看個人喜好了,我覺得五個尺寸就差不多。對於實在特殊的需求也只能特殊對待了。
演示
現在我們測試一下我們所寫的 helper 是不是能夠滿足一般需求,比如一個帶有圓角陰影的用戶卡片,如下:
See the Pen snack-helper-test by Zongbin (@nzbin) on CodePen.
這個實例全部是用 helper 完成的,可惜這套 helper 沒有柵格系統,所以布局並不靈活,但是結合之前的輕量級框架,會顯示出它強大的功能。
總結
編寫 helper 比編寫框架要容易的多,但簡單易用、通俗易懂的 helper 還需要嚴謹的思考,詳細的 helper 可以參見 GitHub 源碼。雖然我一直聲稱沒有打算把 helper 寫成一個框架,但隨着細節的追加與調整,比如添加柵格系統,這個通用的 helper 已經趨向於一個“零件化”的框架了。至於組件式框架和零件式框架哪個更好,這是一個很難選擇的問題。但是我更傾向於組件與零件的結合,因為我不希望整個 HTML 文件被冗長的 CSS 類裝飾的支離破碎。
