如何編寫通用的 Helper Class


Github: https://github.com/nzbin/snack-helper

Docs: https://nzbin.github.io/snack-helper

前言

什么是 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 應該包含哪些內容呢?一般常用的有 paddingmarginfont-sizefont-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 值的話,尺寸型命名就乏力了。我認為,凡是可以量化的屬性,比如 paddingmarginfont-sizeborder-width 等,應該直接用數值表示,而對於不可以量化的屬性,比如 box-shadow,用尺寸型命名比較合適。

精簡命名

大多數的 helpr 命名都是 CSS 屬性的首字母縮寫形式。比如 p 表示 paddingm 表示 marginf-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-roundedrounded 等。我們也可以簡化一下,比如直接用 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 類裝飾的支離破碎。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM