前言
团队要根据新的 UI 规范实现一个组件库,button 组件规范要支持多种主题换肤,字体颜色、背景颜色、边框和禁用使用新的规范,并且一种主题颜色主要组件上使用两种主题颜色混合。另外,增加多一种幽灵按钮类型,经过分析,在 element-ui 的 button 组件上改造麻烦,不好维护,所以需要造一个 button 组件,阅读 element-ui 组件库 button 的源码设计,参考 element-plus css 自定义变量 实现
element-ui 源码分析 button
button 属性
button文档属性 可以定义按钮的尺寸(size),类型(type),朴素样式(plain),圆角(round),圆形(circle),加载(loading),禁用(disabled),图标(icon),是否聚焦(autofocus)等
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|
size | 尺寸 | string | medium / small / mini | — | type | 类型 | string | primary / success / warning / danger / info / text | — | plain | 是否朴素按钮 | boolean | — | false | round | 是否圆角按钮 | boolean | — | false | circle | 是否圆形按钮 | boolean | — | false | loading | 是否加载中状态 | boolean | — | false | disabled | 是否禁用状态 | boolean | — | false | icon | 图标类名 | string | — | — | autofocus | 是否默认聚焦 | boolean | — | false | native-type | 原生 type 属性 | string | button / submit / reset | button |
button 使用
<template>
<div>
<el-button>默认按钮</el-button>
<el-button size="medium">中等按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="text">文字按钮</el-button>
<el-button type="primary" plain>主要按钮</el-button>
<el-button round>主要按钮</el-button>
<el-button icon="el-icon-search" circle></el-button>
<el-button disabled>禁用按钮</el-button>
<el-button type="primary" icon="el-icon-edit">图标按钮</el-button>
<el-button type="primary"
>图标按钮<i class="el-icon-edit el-icon--right"></i
></el-button>
<el-button type="primary" :loading="true">加载中</el-button>
</div>
</template>
Button 源码分析
相关文件路径:
button.vue 文件
button.vue文件路径
props 的属性在文档说明中都有提到,是对组件的扩展
type : 拼接在 el-button-- 后面,生成不同的 class 类,重新定义 color 、background-color 、border-color 覆盖 el-button 默认样式size :外部控制按钮大小,同时可以被表单元素和全局控制,el-button-- + size 类样式,例如 el-button--small icon :支持不同的图标,加载中的图标只能使用 el-icon-loading nativeType :按钮的原生类型,默认是 button ,可以是 submit 、reset 等loading :加载中的状态,is-loading 样式,按钮会被禁用disabled :禁用按钮,is-disabled 类样式,使用 when(disabled) 生成plain :朴素按钮,is-plain 类样式autofocus :自动聚焦,focus 状态样式按钮round :圆角样式 is-round circle :圆心样式 is-circle ,一般配合 icon 图标使用
// button.vue 源码
<template>
<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
}
]"
>
<i class="el-icon-loading" v-if="loading"></i>
<i :class="icon" v-if="icon && !loading"></i>
<span v-if="$slots.default"><slot></slot></span>
</button>
</template>
SCSS 变量文件
common/var.scss 公共变量文件源码 定义公共样式和所有组件样式变量的文件,例如主题颜色、字体颜色、字体大小等,可以通过这个文件实现组件库的换肤
例如主题变量 $--color-primary
mix 函数是将两种颜色按不同的占比混合生成一个新的颜色,例如 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;
$--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default;
$--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default;
$--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default;
$--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default;
$--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default;
$--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default;
$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default;
$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default;
BEM CSS规范
element 的样式规范是使用 bem 管理,根据规范生成类名,避免样式污染,bem 函数的公共代码片段定义在 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简写函数,调用 @include b(button) 参数 $block 赋值 button , 拼接命名空间变量 $namespace el 得到 el-button , !global 改为全局变量,可以给下文使用, @content 占位符插入内容
@mixin b($block) {
$B: $namespace+'-'+$block !global;
.#{$B} {
@content;
}
}
使用 @mixin b 代码片段
@include b(button) {
display: inline-block;
line-height: 1;
}
编译结果是
.el-button {
display: inline-block;
line-height: 1;
}
2、e 是 element 的简写函数,@include e(icon) 调用,$element 传入 icon,在上面 b 函数已经将 $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 修饰符函数,传入 primary , 遍历 $modifier 只有一个元素,遍历结果后 $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 函数
@include b(button) {
@include m(primary) {
}
}
编译结果是
.el-button--primary {
}
button.scss 组件样式
组件库的样式单独单独放在一个目录管理,通过 gulp 打包,源码路径 packages/theme-chalk/src/button.scss
@include b(button) 定义 el-button 类样式,& + & 相邻两个按钮左间距 10px ,button-size 是生成按钮的大小,设计水平、垂直的内边距,字体大小和边框圆角,抽象出方法定义在 packages/theme-chalk/src/mixins/_button
@include b(button) {
// 基本样式,默认样式,在未指定 type 之前
display: inline-block;
line-height: 1;
white-space: nowrap;
cursor: pointer;
background: $--button-default-background-color; // 默认背景色, 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 的用户选择设置
@include utils-user-select(none);
// 兄弟节点之间
& + & {
margin-left: 10px;
}
// 在 packages/theme-chalk/src/mixins/_button,设置按钮边距、字体、边框弧度
@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;
}
// 按钮图标和文字的间距
& [class*="el-icon-"] {
& + span {
margin-left: 5px;
}
}
...
}
when 函数 是定义不同状态的样式,$state-prefix 是 is- ,传入状态类型拼接,例如 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() 函数定义不同的 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 默认按钮的字体颜色、背景颜色和边框颜色,并且定义了伪类 hover 、focus 、active 、disabled 交互状态,颜色变浅通过 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 种按钮大小,默认是最大的按钮,还有 medium 中等、small 小的和 mini 特小的,它是调用 button-size 传入不同的垂直、水平的内间距、字体大小和边框圆角变量值,定义按钮的大小
@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 变透明,设置不同的字体颜色,定义伪类状态
@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;
}
}
}
总结一下,button 按钮的样式变量定义在 commont/var.scss 维护,其他组件也是这种做法,这样就做到只需要修改 var.scss 文件就可以实现组件库的换肤。
按钮的样式规范使用 bem 规范,@include b(button) 定义基础类样式 el-button ;按钮大小是通过 @include button-size(...) 传入内边距、字体大小变量控制显示。
不同的按钮 type 类型、伪类状态还有朴素按钮,通过修改 color 、background-color 、border-color 覆盖默认样式,颜色变浅通过 mix 函数混合白色生成新的颜色,文本按钮、按钮组还有不同的按钮状态根据 bem 规范生成类样式定义。
通过学习优秀的组件库设计,发现处处设计的很巧妙,站在巨人的肩膀上学习。
|