可能被忽略的"按鈕元件"細節
對於元件的開發,我們首推的是在開始寫程式碼之前一定要和設計師 溝通 。看看設計師對於我們元件的拓展是怎麼思考的。以規避程式設計師和設計師拓展方式的不一致導致的設計師一小改程式設計師一大改的 成本問題 。
通常我們需要做一個按鈕的時候,無非就從之前或者別人那裡把程式碼拷貝過來,然後根據設計稿,改改顏色,字號什麼之類的就可以了。
但隨著專案擴張按鈕形態的增加,如果我們一開始就不是全域性的角度去設計元件,到後面就需要花更多的時間去思考按鈕的 拓展性 。
按鈕命名
舉個例子比如我們有一個名為 .btn
的基礎藍色按鈕。然後設計又給到了一個紅色放到 header
區域中的下載按鈕。你會如何拓展這個樣式呢?
.btn-header .btn-red .btn-download
以上三種是我們通常會用到的比較快速的 解決方案 。這三個方案之所以快速,是因為他們都是基於 當下 的 場景 去思考的。我們只是用程式碼描述了這個按鈕和其它按鈕的 區別 而已。
可是關鍵問題來了,既然不同點有這三個,我應該 選擇 哪一個呢?其實我們以上三種都不推薦,但是如果非要在這三個裡選擇一種,我們會推薦 顏色 。
- 首先,如果我們
footer
裡面也需要放一個紅色的下載按鈕,我們顯然不能使用.btn-header
這個名字來拓展我們的按鈕,而應該使用.btn-footer
; - 再者,如果我們在
header
裡面需要新增一個紅色的上傳按鈕,此時顯然我們.btn-upload
更滿足我們的需求;
可是這樣就達不到我們要 複用 樣式的目的了。如果是基於顏色拓展的話,你依然可以使用 .btn-red
這個樣式來表示在 footer
中的紅色下載按鈕。因為我們要複用的是 樣式 ,而紅色本身就是一種樣式,自然拓展性會更好一點。再者按照我們的實際開發的經驗,顏色的改動明顯是遠遠低於我們位置和功能的。甚至有很多的專案,一旦主題色定下之後,基本上是不會改的。基於這個點推薦大家可以看一下張鑫旭老師的 基於CSS color屬性的靜態UI元件重構策略 的這篇文章。
當然改動概率很小,並不代表不會改變。或者說我們的設計師在已經有一個紅色按鈕的情況下,又設計了另一個紅色的按鈕,此時你會怎麼命名另一個紅色的按鈕?難道要叫 .btn-red2
?
按鈕設計應該回歸設計
此時就引出了另外一個問題,我們按鈕的 UI 是設計師給的,所以對於按鈕的拓展方式我們是需要和設計師 溝通 的。就像上面的問題,如果設計師給到了兩個紅色的按鈕,那麼可以和設計討論是否考慮用其它的顏色替代。
既然是要溝通,就不能所有的拓展邏輯都是完全基於設計同學的思維方式來。我們得拿出我們的 方案 。好在的是在當下的環境中,已經有比較好的 最佳實踐 了。這其中首推的是目前最火的也是火了很多年的前端 UI 框架 Bootstrap 。他們對於按鈕的封裝,也幾乎成為了 國際通用 的拓展規則。
這邊基於 Bootstrap 按鈕元件,結合我們實際的經驗帶大家看看,按鈕的封裝和拓展邏輯。文中的程式碼是為了解釋原理的虛擬碼,實際開發會為了減少程式碼量,不會寫得這麼囉嗦。
程式碼結構
_var.scss// 引數 _base.scss// 基礎樣式 _theme.scss// 主題 _size.scss// 尺寸 _shape.scss// 形狀 _status.scss// 狀態 index.scss// 引用以上所有檔案 複製程式碼
分類
按鈕基礎樣式設定完成之後,我們要做的就是思考按鈕的拓展了。要拓展按鈕,首先要先看看按鈕的分類有哪些。
型別 | 型別細分 |
---|---|
按鈕主題 | 主按鈕 primary , 次按鈕 secondary , 成功按鈕 success , 危險按鈕 danger , 警告按鈕 warning |
按鈕大小 | 比大更大 largex , 大按鈕 large , 預設按鈕 default , 中號按鈕 middle , 小按鈕 small , 比小更小 smallx |
按鈕形狀 | 連結按鈕 link , 幽靈按鈕 ghost , 膠囊按鈕 capsule , 塊狀按鈕 block |
按鈕狀態 | 禁用 disabled , 滑鼠移入 hover , 滑鼠按下 active , 獲取焦點 focus , 載入 loading |
基本上我們按鈕主要可以分為以上四大類,而以上的幾大類又可以互相的排列組合。
比如 disabled
, warning
, ghost
, large
可以表示一個禁用狀態下的警告幽靈大按鈕。
原生CSS
<button type="button" disabled class="btn _warning _ghost _large">warning按鈕</button> <a href="javascript:;" class="btn _warning _disabled _ghost _large">warning按鈕</a> 複製程式碼
在 CSS規範 中有提到通過是用下滑線作為字首的命名規則。
元件化框架
<Button warning disabled ghost large>React按鈕</Button> 複製程式碼
在類似 React 和 VUE 的場景中我們推薦直接使用單屬性的方式拓展我們們的元件,當然元件內部的實現可以採用和原生 CSS 一樣的邏輯。
看到這裡有的同學可能會對於我們的拓展方式感到某些疑惑,因為從可讀性來說以下的方式顯然更加的優雅。
<Button theme="warning" status="disabled" shape="ghost" size="large">按鈕</Button> 複製程式碼
對於這個問題我們內部也有一些討論,這種寫法是更加的具有可讀性,而且自創的這些名稱也不容易汙染我們的按鈕屬性。但是基於以下 3 點考慮,我們最終還是選擇了更扁平化的 單屬性控制 邏輯。
- 原生: 對於 API 的設計通常我們更青睞沿用原生的方式,而不是自創一種我們以為更加優雅的邏輯(原生對於按鈕
disabled
的拓展方式是使用的<button disabled>button</button>
)。 - 方便使用方: 在我們實際的使用的時候,我們只需要知道屬性名,而不需要記住它的分類。通常我們也不會同時在一個按鈕上操作這麼多的屬性,一到兩個就已經差不多了。(比如我問
_ghost
這個按鈕應該是什麼分類?我相信對於不熟悉我們按鈕的分類的同學其實是很難反應的出它是屬於我們shape
這個分類的)。 - 拓展性: 對於開發者來說,上面提到的不用糾結分類的問題同樣是適用。並且在自己想要拓展一些自定義按鈕樣式的時候,也不需要去考慮它應該屬於什麼分類。再者,我們按鈕的不同屬性之間是可以排列組合的。但是如果採用的 鍵值 的方的話,我們很難實現同分類下的按鈕屬性組合。(比如我們想同時使用
shape
的這個分類當中的block
和ghost
這兩個狀態)。
按鈕基礎樣式 「 _base.scss 」
.btn { /* a 連結預設為inline元素,但也有可能顯示為按鈕所以設定 inline-block 屬性 */ display: inline-block; /* 按鈕文字居中,特別是當我們給了按鈕一個固定寬度的時候 */ text-align: center; /* 按鈕文字不換行 */ white-space: nowrap; /* 去掉可以用滑鼠選中按鈕上的文字功能,沒有這個屬性選中的時候會出現一個比較難看的半透明框 */ user-select: none; /* 為非可選標籤,新增滑鼠手型。大多數瀏覽器對於 a 標籤和 button 標籤預設是有這個屬性的,但其它標籤就不一定了 */ cursor: pointer; /* 按鈕和文字混排的時候近似垂直居中 */ vertical-align: middle; /* 讓padding 和 border 的寬度不影響按鈕大小,IE8 和 IE8 以上才相容這個屬性 */ box-sizing: border-box; /* 按鈕需要設計字型,這裡為了保持統一建議和全站主字型保持一致 */ /* 但是通常我們在 css reset 中會做這一步的重置,所以這裡不需要了 */ /* font-family:inherit; */ /* 以下樣式根據實際設計情況來編輯 */ /* 按鈕圓角 */ border-radius: 3px; /* 去掉按鈕的預設邊框 */ border: none 0; } 複製程式碼
對於按鈕基礎樣式,因為沒有涉及到按鈕的拓展性,所以大家的樣式基本都大同小異。
按鈕主題 「 _theme.scss 」
按鈕主題其實是按照功能區分,只是設計師通常用顏色區分功能,所以主題也近似可以看作是顏色的區分。
Bootstrap 是一個沒有特定產品的通用基礎框架,即使在按鈕設計極致收斂的情況下,仍然有 Primary
, Secondary
, Success
, Danger
, Warning
, Info
, Light
, Dark
, Link
九種主題(在我們看來 link
狀態的按鈕也有 primary
的作用,所以不同於 Bootstrap 我們把 Link
歸類到了形狀 shape
這個分類中)。
對於我們自己的產品來說,這麼多的分類是不推薦的。我們期望的是用更少的主題適應更多的場景,要達到這一點,也是需要多和設計師溝通的。以我們的經驗, 主按鈕 primary
, 次按鈕 secondary
, 成功按鈕 success
, 危險按鈕 danger
, 警告按鈕 warning
這5種主題已經能涵蓋很大一部分場景了。
.btn._primary{ color:$c_priamry; background-color:$c_primary; } 複製程式碼
按鈕的主題色,在實際開發中我們的顏色應該是基於全域性的顏色引數去獲取的。對於全域性顏色引數的命名,我們推薦使用 c_
字首。
按鈕大小 「 _size.scss 」
比大更大 largex
, 大按鈕 large
, 預設按鈕 default
, 中號按鈕 middle
, 小按鈕 small
, 比小更小 smallx
...
在大小的數量上和主題邏輯是一樣的,建議使用更少的大小,適配更多的場景,我們推薦使用大,中,小,加預設共計四種樣式。
當然如果要拓展大話,我們建議通過類似衣服尺碼 xs, xl 新增 x 的方式進行拓展 _largex。
對於按鈕尺寸是設定邏輯,我們建議遵從 Metiral Design 的 8 point 規則(尺寸控制在 8 畫素的倍數,實在不能滿足也應該至少是 4 的倍數)。
.btn{ height: 40px; font-size: 16px; line-height: 24px; padding: 8px 16px; } .btn._middle{ height: 32px; font-size: 14px; line-height: 24px; padding: 4px 12px; } 複製程式碼
對於大小,應該不只是按鈕的高寬的變化,同時應該需要考慮到按鈕字號的變化,這樣才會更加的協調。
按鈕形狀 「 _shape.scss 」
實心按鈕 fill
, 連結按鈕 link
, 幽靈按鈕 ghost
, 膠囊按鈕 capsule
, 塊狀按鈕 block
...
按鈕的形狀,基本上業界常用的是以上五種方式,當然也不排除設計有定製的需求。
實心按鈕 fill

.btn._fill{ color:#fff; } 複製程式碼
背景是主題色,文字是白色的按鈕,因為太常用所以一般作為預設按鈕的樣式,所以在實際開發種我們不會另起一個 fill
的屬性。
連結按鈕 link

.btn._link{ background-color: transparent; } 複製程式碼
文字是主題色,背景為透明的按鈕,雖然看起來是文字,但是它和其它按鈕佔據同樣大小的空間。
幽靈按鈕 ghost

.btn._ghost{ background-color: transparent; border:1px solid; /* 相容邊框增加引起的文字偏移 */ line-height: 24px - 1px ; } 複製程式碼
文字和邊框是主題色,背景為透明的按鈕。
border
會預設使用文字的邊框顏色,這裡因為給按鈕設定了邊框,但是因為按鈕高度是寫死的,那麼意味著,這裡的文字會被往下推 1 個畫素,這邊需要對於不同的按鈕做一個相容。
膠囊按鈕 capsule

.btn._capsule{ border-radius:100%; } 複製程式碼
左右兩邊是圓角的按鈕。
塊狀按鈕 block

.btn._block{ display:block; width:100%; /* 如果沒有給按鈕設定 box-sizing:border-box; 屬性,此處還應該去掉按鈕左右間距。 */ /* padding-left:0; */ /* padding-right:0; */ } 複製程式碼
佔一行的按鈕。
按鈕狀態 [_status.scss]
:disabled 禁用狀態
, :hover 滑鼠移入
, :active 滑鼠按下
, :focus 獲取焦點
, ._loading 載入狀態
...
按鈕處於一些臨界點的時候需要有一些特殊的狀樣式告知使用者,按鈕通常有以上的五個狀態。
/* 偷懶但簡潔 */ .btn{ transition:200ms; } /* 繁瑣但效能更好 */ .btn{ transition:opacity 200ms, background-color 200ms, color 200ms; } 複製程式碼
因為按鈕的狀態切換,通常是從一個狀態到另一個狀態,為了讓這個狀態過度的更加自然,建議新增上 transition
屬性。
:disabled 禁用狀態

.btn:disabled, .btn._disabled { /* 用css的方式讓元素不可被選中,不支援該屬性的需要用 js 阻止事件提交 */ pointer-events: none; /* 修改滑鼠手型為不允許 */ cursor: not-allowed; /* 修改透明度 */ opacity: 0.5; } 複製程式碼
按鈕不可用狀態,通常是某些只執行一次的操作,在操作完成之後的狀態。或者是需要某些特定觸發條件才能啟用按鈕。 禁用狀態一般比正常按鈕看起來要更弱一點。最簡單的做法是降低透明度,這樣的好處是不用給每個主題單獨去設定一個禁用狀態的顏色。 當然每個主題單獨設定的視覺效果會更好。
並且 button
標籤如果有 disalbed
屬性還可以阻止表單的提交。
:hover
滑鼠移入

/* 滑鼠移入狀態 */ .btn._primary:hover{ background-color: darken($c_priamry,10%); color: darken($c_priamry,10%); } 複製程式碼
滑鼠移入的狀態和 disabled
的效果會有點相反,通常會讓按鈕變得更重一點。為了統一,我們建議使用,css 前處理器的 darken
函式,來讓我們的主題色,加深 10%
。
:active
滑鼠按下

/* 滑鼠移入狀態 */ .btn:active{ transform:scale(0.98); } 複製程式碼
active
是緊接著 hover
的一個狀態,所以偷懶的話我們忽略這個狀態,直接沿用 hover
的狀態。但是像做得更好的 Metiarl Design 他們採用的是漣漪水波的效果。而我們這邊採用了更簡單的按下變小的邏輯「 感覺很像你拿手把按鈕壓扁了 」。這個效果可以和設計師溝通,權衡一下收益。
:focus 獲取焦點
button{ outline:none; } 複製程式碼
focus
也是容易被大家忽略,甚至是為了視覺效果而被捨棄。因為主流瀏覽器預設的 focus
狀態一般會是一個藍色漸變的陰影,通常來說設計師會認為不好看。於是我們經常會被要求用以上的程式碼去掉瀏覽器預設的行為。
對於這一點我們是非常不推崇的,因為 focus
對於無障礙訪問是非常重要的時候,當你的滑鼠不能使用的時候,在有 focus
的狀態下,別人也知道當前焦點的位置,從而也能進行操作。偷懶的做法是我們什麼都不做,沿用瀏覽器的預設行為,如果設計師說不好看,那請麻煩設計師拿出好看的替代方案,而不是直接去掉。
:loading
載入狀態

除了以上 4 個預設的瀏覽器按鈕狀態。通常情況下,在按鈕按下之後等待 Ajax 請求結果的這段時間,我們會通過一個額外的 Loading
狀態來告訴使用者此時正在載入。並且在這段時間,一般不允許使用者的二次操作,所以我們此時的狀態建議是基於 disabled
的拓展。
此時採用 GIF 動圖會是一個體驗比較好的方案,但是因為我們有多種主題和其它的狀態,我們就需要對不同狀態做不同的 GIF 這難免有點繁瑣。所以我們通常建議是使用CSS 動畫來處理這部分的邏輯。
在我們專案裡,我們採用了兩個小圈的效果「 類似於大白眨眼睛的效果 」。因為我們能簡單操控的偽元素,有 before
, after
所以在和設計師溝通的時候我們希望只操作兩個元素。
高階按鈕
以上的按鈕都是我們國際通用的標準化的形態。當然在我們實際的工作狀態中,我們不可能只有這麼單一形態的按鈕。很多時候我們會有基於這些按鈕拓展出的新的高階按鈕。

對於這樣的按鈕,我們不推薦直接在以上的標準的按鈕中相容這些邏輯,哪怕只是在這個基礎按鈕中就加一行程式碼就實現了拓展。而應該是繼承這個按鈕拓展出一個新的按鈕。
<Button small warning>基礎按鈕</Button> <ButtonPop small warning popNum="3">高階氣泡按鈕</ButtonPop> 複製程式碼
- 程式碼膨脹率 隨著我們按鈕形態的豐富,基礎按鈕程式碼膨脹率的也不會不受控的增長;
- 程式碼耦合 我要刪除一個高階按鈕,我只需要直接刪除這個高階元件按鈕就好,而不需要在基礎按鈕樣式中去找關於這個高階按鈕邏輯的程式碼;
END
因為按鈕已經有了很多 最佳實踐 ,我們很容易進入拿來主義的誤區。然後在開發到後期的時候發現,這個最佳實踐和我們實際專案不一定是 Match ,然後進入到縫縫補補的狀態。所以最後給大家的建議是前期還是儘量多花一點時間和設計溝通,和組內其它同學溝通。就是適合自己的就是最好的。