前言
團(tuán)隊要根據(jù)新的 UI 規(guī)范實現(xiàn)一個組件庫挟炬,button
組件規(guī)范要支持多種主題換膚焰络,字體顏色戴甩、背景顏色、邊框和禁用使用新的規(guī)范闪彼,并且一種主題顏色主要組件上使用兩種主題顏色混合甜孤。另外协饲,增加多一種幽靈按鈕類型,經(jīng)過分析缴川,在 element-ui 的 button
組件上改造麻煩茉稠,不好維護(hù),所以需要造一個 button 組件把夸,閱讀 element-ui 組件庫 button
的源碼設(shè)計而线,參考 element-plus css 自定義變量
實現(xiàn)
element-ui 源碼分析 button
button 屬性
button文檔屬性 可以定義按鈕的尺寸(size),類型(type)恋日,樸素樣式(plain)膀篮,圓角(round),圓形(circle)岂膳,加載(loading)誓竿,禁用(disabled),圖標(biāo)(icon)谈截,是否聚焦(autofocus)等
參數(shù) | 說明 | 類型 | 可選值 | 默認(rèn)值 |
---|---|---|---|---|
size | 尺寸 | string | medium / small / mini | — |
type | 類型 | string | primary / success / warning / danger / info / text | — |
plain | 是否樸素按鈕 | boolean | — | false |
round | 是否圓角按鈕 | boolean | — | false |
circle | 是否圓形按鈕 | boolean | — | false |
loading | 是否加載中狀態(tài) | boolean | — | false |
disabled | 是否禁用狀態(tài) | boolean | — | false |
icon | 圖標(biāo)類名 | string | — | — |
autofocus | 是否默認(rèn)聚焦 | boolean | — | false |
native-type | 原生 type 屬性 | string | button / submit / reset | button |
button 使用
<template>
<div>
<el-button>默認(rèn)按鈕</el-button>
<!-- 尺寸(size):medium(中等)筷屡,small(小型),mini(超恤の埂) -->
<el-button size="medium">中等按鈕</el-button>
<!-- 類型(type):default(默認(rèn))毙死,primary(主要),success(成功)娘赴,info(信息)规哲,warning(警告),danger(危險)诽表,text(文本) -->
<el-button type="primary">主要按鈕</el-button>
<el-button type="text">文字按鈕</el-button>
<!-- 樸素(plain):true唉锌,false -->
<el-button type="primary" plain>主要按鈕</el-button>
<!-- 圓角(round):true,false -->
<el-button round>主要按鈕</el-button>
<!-- 圓形(circle):true竿奏,false袄简。一般與圖標(biāo) icon 連用 -->
<el-button icon="el-icon-search" circle></el-button>
<!-- 禁用(disabled):true,false泛啸。按鈕顏色變淺绿语,鼠標(biāo)懸浮出現(xiàn)禁止符號。 -->
<el-button disabled>禁用按鈕</el-button>
<!-- 圖標(biāo)(icon):設(shè)置icon屬性即可候址,可以參考 icon 組件吕粹。設(shè)置在文字右邊的 icon 岗仑,需要使用i標(biāo)簽驶赏,此時最好添加上 el-icon--right 類蚯姆。 -->
<el-button type="primary" icon="el-icon-edit">圖標(biāo)按鈕</el-button>
<el-button type="primary"
>圖標(biāo)按鈕<i class="el-icon-edit el-icon--right"></i
></el-button>
<!-- 加載(loading):true桐玻,false。為 true 時點擊事件失效。 -->
<el-button type="primary" :loading="true">加載中</el-button>
</div>
</template>
Button 源碼分析
相關(guān)文件路徑:
- packages/button/src/button.vue button vue 單文件組件
- packages/theme-chalk/src/common/var.scss#L506-L591 按鈕 SCSS 變量名文件
- packages/theme-chalk/button.scss 按鈕樣式文件
-
packages/theme-chalk/mixins/_button.scss 按鈕混入樣式文件轿衔,公共混入代碼蛤育,例如按鈕不同
type
樣式咕娄、按鈕plain
樸素樣式、按鈕size
大小樣式 -
packages/theme-chalk/minxins.scss 混入樣式文件胡控, 包括
bem
規(guī)范锡搜、when
狀態(tài) mixin 代碼片段,例如b(button)
會轉(zhuǎn)化為el-button
button.vue 文件
props
的屬性在文檔說明中都有提到明未,是對組件的擴(kuò)展
-
type
: 拼接在el-button--
后面亲雪,生成不同的class
類行疏,重新定義color
匆光、background-color
、border-color
覆蓋el-button
默認(rèn)樣式 -
size
:外部控制按鈕大小酿联,同時可以被表單元素和全局控制终息,el-button--
+ size 類樣式,例如el-button--small
-
icon
:支持不同的圖標(biāo)贞让,加載中的圖標(biāo)只能使用el-icon-loading
-
nativeType
:按鈕的原生類型周崭,默認(rèn)是button
,可以是submit
喳张、reset
等 -
loading
:加載中的狀態(tài)续镇,is-loading
樣式,按鈕會被禁用 -
disabled
:禁用按鈕销部,is-disabled
類樣式摸航,使用when(disabled)
生成 -
plain
:樸素按鈕制跟,is-plain
類樣式 -
autofocus
:自動聚焦,focus
狀態(tài)樣式按鈕 -
round
:圓角樣式is-round
-
circle
:圓心樣式is-circle
酱虎,一般配合icon
圖標(biāo)使用
// button.vue 源碼
<template>
<!-- 原生的 button 進(jìn)行樣式封裝雨膨。 -->
<button
class="el-button"
@click="handleClick"
:disabled="buttonDisabled || loading"
:autofocus="autofocus"
:type="nativeType"
:class="[
type ? 'el-button--' + type : '',
buttonSize ? 'el-button--' + buttonSize : '',
{
'is-disabled': buttonDisabled,
'is-loading': loading,
'is-plain': plain,
'is-round': round,
'is-circle': circle
}
]"
>
<!--
1. el-icon-loading 是加載圖標(biāo)類,
2. 這里使用了兩個 <i></i> 都是用來放圖標(biāo)的读串,會根據(jù) loading 的值進(jìn)行二選一
并且 loading = ture 加載狀態(tài)無法使用其他圖標(biāo)聊记,同時外層 button 被設(shè)置為 disabled
-->
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<!-- 按鈕文本插槽,default 屬性包括了所有沒有被包含在具名插槽中的節(jié)點 -->
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
SCSS 變量文件
common/var.scss 公共變量文件源碼 定義公共樣式和所有組件樣式變量的文件恢暖,例如主題顏色排监、字體顏色、字體大小等杰捂,可以通過這個文件實現(xiàn)組件庫的換膚
例如主題變量 $--color-primary
mix
函數(shù)是將兩種顏色按不同的占比混合生成一個新的顏色舆床,例如 mix($--color-white, $--color-primary, 10%)
, $--color-white
顏色占比 10%,$--color-primary
占比 90%琼娘,生成一種新的顏色
$--color-primary: #409EFF !default;
/// color|1|Background Color|4
$--color-white: #FFFFFF !default;
/// color|1|Background Color|4
$--color-black: #000000 !default;
$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
$--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */
$--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */
$--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */
$--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */
$--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */
$--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */
$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */
$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default;
BEM CSS規(guī)范
element 的樣式規(guī)范是使用 bem 管理峭弟,根據(jù)規(guī)范生成類名,避免樣式污染脱拼,bem 函數(shù)的公共代碼片段定義在 packages/theme-chalk/src/mixins/mixins.scss 文件
theme-chalk/src/mixins/config.scss 定義命名空間
$namespace: 'el';
$element-separator: '__';
$modifier-separator: '--';
$state-prefix: 'is-';
1瞒瘸、b
是 block
的 mixin簡寫函數(shù),調(diào)用 @include b(button)
參數(shù) $block
賦值 button
, 拼接命名空間變量 $namespace
el 得到 el-button
, !global
改為全局變量熄浓,可以給下文使用情臭, @content
占位符插入內(nèi)容
@mixin b($block) {
$B: $namespace+'-'+$block !global;
.#{$B} {
@content;
}
}
使用 @mixin b
代碼片段
@include b(button) {
display: inline-block;
line-height: 1;
}
編譯結(jié)果是
.el-button {
display: inline-block;
line-height: 1;
}
2、e
是 element 的簡寫函數(shù)赌蔑,@include e(icon)
調(diào)用俯在,$element
傳入 icon,在上面 b
函數(shù)已經(jīng)將 $B
賦值為全局變量 el-button
娃惯,$currentSelector
拼接后得到 .el-button__icon
,@at-root 是跳出嵌套趾浅,和 .el-button
同級愕提,而不是 .el-button .el-button-icon
拼接在后面
@mixin e($element) {
$E: $element !global;
$selector: &;
$currentSelector: "";
@each $unit in $element {
$currentSelector: #{$currentSelector + "." + $B + $element-separator + $unit + ","};
}
@if hitAllSpecialNestRule($selector) {
@at-root {
#{$selector} {
#{$currentSelector} {
@content;
}
}
}
} @else {
@at-root {
#{$currentSelector} {
@content;
}
}
}
}
3、m
修飾符函數(shù)皿哨,傳入 primary
, 遍歷 $modifier
只有一個元素浅侨,遍歷結(jié)果后 $currentSelector
賦值是 &--primary
,在scss 編譯证膨, & 是上級類 el-button
如输,編輯是 el-button--primary
@mixin m($modifier) {
$selector: &;
$currentSelector: "";
@each $unit in $modifier {
$currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
使用 m
函數(shù)
@include b(button) {
@include m(primary) {
}
}
編譯結(jié)果是
.el-button--primary {
}
button.scss 組件樣式
組件庫的樣式單獨單獨放在一個目錄管理,通過 gulp
打包,源碼路徑 packages/theme-chalk/src/button.scss
@include b(button)
定義 el-button
類樣式不见,& + &
相鄰兩個按鈕左間距 10px
澳化,button-size
是生成按鈕的大小,設(shè)計水平脖祈、垂直的內(nèi)邊距肆捕,字體大小和邊框圓角,抽象出方法定義在 packages/theme-chalk/src/mixins/_button
@include b(button) {
// 基本樣式盖高,默認(rèn)樣式,在未指定 type 之前
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: $--button-default-background-color; // 默認(rèn)背景色眼虱, white
border: $--border-base; // 1px solid #DCDFE6
border-color: $--button-default-border-color; // #DCDFE6
color: $--button-default-font-color; // #606266
-webkit-appearance: none;
text-align: center;
box-sizing: border-box;
outline: none;
margin: 0;
transition: .1s;
font-weight: $--button-font-weight; // 500
// 在 packages/theme-chalk/src/mixins/utils 喻奥,主要是 moz,webkit,ms 的用戶選擇設(shè)置
@include utils-user-select(none);
// 兄弟節(jié)點之間
& + & {
margin-left: 10px;
}
// 在 packages/theme-chalk/src/mixins/_button,設(shè)置按鈕邊距捏悬、字體撞蚕、邊框弧度
@include button-size($--button-padding-vertical, $--button-padding-horizontal, $--button-font-size, $--button-border-radius);
// 懸浮、聚焦按鈕樣式
&:hover,
&:focus {
color: $--color-primary;
border-color: $--color-primary-light-7;
background-color: $--color-primary-light-9;
}
&::-moz-focus-inner {
border: 0;
}
// 按鈕圖標(biāo)和文字的間距
& [class*="el-icon-"] {
& + span {
margin-left: 5px;
}
}
...
}
when 函數(shù) 是定義不同狀態(tài)的樣式过牙,$state-prefix
是 is-
甥厦,傳入狀態(tài)類型拼接,例如 when(loading)
是 .el-button.is-loading
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
when(plain)
寇钉、when(active)
刀疙、when(disabled)
、when(loading)
扫倡、when(round)
谦秧、when(circle)
分別定義 is-plain
,is-active
撵溃、is-disabled
疚鲤、is-loading
、is-round
缘挑、is-circle
集歇,這也是 button prop
傳入的屬性,生成不同的類樣式顯示
@include when(plain) {
&:hover,
&:focus {
background: $--color-white;
border-color: $--color-primary;
color: $--color-primary;
}
&:active {
background: $--color-white;
border-color: mix($--color-black, $--color-primary, $--button-active-shade-percent);
color: mix($--color-black, $--color-primary, $--button-active-shade-percent);
outline: none;
}
}
@include when(active) {
color: mix($--color-black, $--color-primary, $--button-active-shade-percent);
border-color: mix($--color-black, $--color-primary, $--button-active-shade-percent);
}
@include when(disabled) {
&,
&:hover,
&:focus {
color: $--button-disabled-font-color;
cursor: not-allowed;
background-image: none;
background-color: $--button-disabled-background-color;
border-color: $--button-disabled-border-color;
}
&.el-button--text {
background-color: transparent;
}
&.is-plain {
&,
&:hover,
&:focus {
background-color: $--color-white;
border-color: $--button-disabled-border-color;
color: $--button-disabled-font-color;
}
}
}
@include when(loading) {
position: relative;
pointer-events: none;
&:before {
pointer-events: none;
content: '';
position: absolute;
left: -1px;
top: -1px;
right: -1px;
bottom: -1px;
border-radius: inherit;
background-color: rgba(255,255,255,.35);
}
}
@include when(round) {
border-radius: 20px;
padding: 12px 23px;
}
@include when(circle) {
border-radius: 50%;
padding: $--button-padding-vertical;
}
例如 is-plain
樣式
組件傳入 type
语淘,是通過下面 m()
函數(shù)定義不同的 class诲宇,button-variant
代碼片段是定義在 mixins/_button,
@include m(primary) {
@include button-variant($--button-primary-font-color, $--button-primary-background-color, $--button-primary-border-color);
}
@include m(success) {
@include button-variant($--button-success-font-color, $--button-success-background-color, $--button-success-border-color);
}
@include m(warning) {
@include button-variant($--button-warning-font-color, $--button-warning-background-color, $--button-warning-border-color);
}
@include m(danger) {
@include button-variant($--button-danger-font-color, $--button-danger-background-color, $--button-danger-border-color);
}
@include m(info) {
@include button-variant($--button-info-font-color, $--button-info-background-color, $--button-info-border-color);
}
button-variant
傳入不同的 color
、background-color
亏娜、border-color
變量覆蓋 default
默認(rèn)按鈕的字體顏色焕窝、背景顏色和邊框顏色,并且定義了偽類 hover
维贺、focus
它掂、active
、disabled
交互狀態(tài),顏色變淺通過 mix
和 白色
混合
@mixin button-variant($color, $background-color, $border-color) {
color: $color;
background-color: $background-color;
border-color: $border-color;
&:hover,
&:focus {
background: mix($--color-white, $background-color, $--button-hover-tint-percent);
border-color: mix($--color-white, $border-color, $--button-hover-tint-percent);
color: $color;
}
&:active {
background: mix($--color-black, $background-color, $--button-active-shade-percent);
border-color: mix($--color-black, $border-color, $--button-active-shade-percent);
color: $color;
outline: none;
}
&.is-active {
background: mix($--color-black, $background-color, $--button-active-shade-percent);
border-color: mix($--color-black, $border-color, $--button-active-shade-percent);
color: $color;
}
&.is-disabled {
&,
&:hover,
&:focus,
&:active {
color: $--color-white;
background-color: mix($background-color, $--color-white);
border-color: mix($border-color, $--color-white);
}
}
&.is-plain {
@include button-plain($background-color);
}
}
&.is-plain
類覆蓋主要按鈕的顏色得到樸素按鈕虐秋,使用 button-plain($background-color)
定義在同一個文件榕茧,偽類也是定義 color
、background-color
客给、border-color
覆蓋
@mixin button-plain($color) {
color: $color;
background: mix($--color-white, $color, 90%);
border-color: mix($--color-white, $color, 60%);
&:hover,
&:focus {
background: $color;
border-color: $color;
color: $--color-white;
}
&:active {
background: mix($--color-black, $color, $--button-active-shade-percent);
border-color: mix($--color-black, $color, $--button-active-shade-percent);
color: $--color-white;
outline: none;
}
&.is-disabled {
&,
&:hover,
&:focus,
&:active {
color: mix($--color-white, $color, 40%);
background-color: mix($--color-white, $color, 90%);
border-color: mix($--color-white, $color, 80%);
}
}
}
button
提供 4 種按鈕大小用押,默認(rèn)是最大的按鈕,還有 medium
中等靶剑、small
小的和 mini
特小的蜻拨,它是調(diào)用 button-size
傳入不同的垂直、水平的內(nèi)間距桩引、字體大小和邊框圓角變量值缎讼,定義按鈕的大小
@include m(medium) {
@include button-size($--button-medium-padding-vertical, $--button-medium-padding-horizontal, $--button-medium-font-size, $--button-medium-border-radius);
@include when(circle) {
padding: $--button-medium-padding-vertical;
}
}
@include m(small) {
@include button-size($--button-small-padding-vertical, $--button-small-padding-horizontal, $--button-small-font-size, $--button-small-border-radius);
@include when(circle) {
padding: $--button-small-padding-vertical;
}
}
@include m(mini) {
@include button-size($--button-mini-padding-vertical, $--button-mini-padding-horizontal, $--button-mini-font-size, $--button-mini-border-radius);
@include when(circle) {
padding: $--button-mini-padding-vertical;
}
}
button-size
代碼定義在 mixin/_button.scss
@mixin button-size($padding-vertical, $padding-horizontal, $font-size, $border-radius) {
padding: $padding-vertical $padding-horizontal;
font-size: $font-size;
border-radius: $border-radius;
&.is-round {
padding: $padding-vertical $padding-horizontal;
}
}
按鈕支持文本類型 el-button--text
,將 border
坑匠、background
變透明血崭,設(shè)置不同的字體顏色,定義偽類狀態(tài)
@include m(text) {
border-color: transparent;
color: $--color-primary;
background: transparent;
padding-left: 0;
padding-right: 0;
&:hover,
&:focus {
color: mix($--color-white, $--color-primary, $--button-hover-tint-percent);
border-color: transparent;
background-color: transparent;
}
&:active {
color: mix($--color-black, $--color-primary, $--button-active-shade-percent);
border-color: transparent;
background-color: transparent;
}
&.is-disabled,
&.is-disabled:hover,
&.is-disabled:focus {
border-color: transparent;
}
}
}
總結(jié)一下厘灼,button
按鈕的樣式變量定義在 commont/var.scss
維護(hù)夹纫,其他組件也是這種做法,這樣就做到只需要修改 var.scss
文件就可以實現(xiàn)組件庫的換膚设凹。
按鈕的樣式規(guī)范使用 bem
規(guī)范舰讹,@include b(button)
定義基礎(chǔ)類樣式 el-button
;按鈕大小是通過 @include button-size(...)
傳入內(nèi)邊距围来、字體大小變量控制顯示跺涤。
不同的按鈕 type
類型、偽類狀態(tài)還有樸素按鈕监透,通過修改 color
桶错、background-color
、border-color
覆蓋默認(rèn)樣式胀蛮,顏色變淺通過 mix
函數(shù)混合白色生成新的顏色院刁,文本按鈕、按鈕組還有不同的按鈕狀態(tài)根據(jù) bem
規(guī)范生成類樣式定義粪狼。
通過學(xué)習(xí)優(yōu)秀的組件庫設(shè)計退腥,發(fā)現(xiàn)處處設(shè)計的很巧妙,站在巨人的肩膀上學(xué)習(xí)再榄。