前言

团队要依据新的 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尺寸stringmedium / small / mini
type类型stringprimary / success / warning / danger / info / text
plain是否奢侈按钮booleanfalse
round是否圆角按钮booleanfalse
circle是否圆形按钮booleanfalse
loading是否加载中状态booleanfalse
disabled是否禁用状态booleanfalse
icon图标类名string
autofocus是否默认聚焦booleanfalse
native-type原生 type 属性stringbutton / submit / resetbutton

button 应用

<template>  <div>    <el-button>默认按钮</el-button>    <!-- 尺寸(size):medium(中等),small(小型),mini(超小) -->    <el-button size="medium">中等按钮</el-button>    <!-- 类型(type):default(默认),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。个别与图标 icon 连用 -->    <el-button icon="el-icon-search" circle></el-button>    <!-- 禁用(disabled):true,false。按钮色彩变浅,鼠标悬浮呈现禁止符号。 -->    <el-button disabled>禁用按钮</el-button>    <!-- 图标(icon):设置icon属性即可,能够参考 icon 组件。设置在文字左边的 icon ,须要应用i标签,此时最好增加上 el-icon--right 类。 -->    <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>    <!-- 加载(loading):true,false。为 true 时点击事件生效。 -->    <el-button type="primary" :loading="true">加载中</el-button>  </div></template>

Button 源码剖析

相干文件门路:

  • 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 标准、when状态 mixin 代码片段,例如 b(button) 会转化为 el-button

button.vue 文件

button.vue文件门路

props 的属性在文档阐明中都有提到,是对组件的扩大

  • type: 拼接在 el-button-- 前面,生成不同的 class 类,从新定义 colorbackground-colorborder-color 笼罩 el-button 默认款式
  • size:内部管制按钮大小,同时能够被表单元素和全局管制,el-button-- + size 类款式,例如 el-button--small
  • icon:反对不同的图标,加载中的图标只能应用 el-icon-loading
  • nativeType:按钮的原生类型,默认是 button,能够是 submitreset
  • loading:加载中的状态,is-loading 款式,按钮会被禁用
  • disabled:禁用按钮,is-disabled 类款式,应用 when(disabled) 生成
  • plain:奢侈按钮,is-plain类款式
  • autofocus:主动聚焦,focus 状态款式按钮
  • round:圆角款式 is-round
  • circle:圆心款式 is-circle,个别配合 icon 图标应用
// button.vue 源码<template>  <!-- 原生的 button 进行款式封装。 -->  <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 是加载图标类,      2. 这里应用了两个 <i></i> 都是用来放图标的,会依据 loading 的值进行二选一         并且 loading = ture 加载状态无奈应用其余图标,同时外层 button 被设置为 disabled    -->    <i class="el-icon-loading" v-if="loading"></i>    <i :class="icon" v-if="icon && !loading"></i>    <!-- 按钮文本插槽,default 属性包含了所有没有被蕴含在具名插槽中的节点 -->    <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; /* 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标准

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、bblock 的 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 类款式,& + & 相邻两个按钮左间距 10pxbutton-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-prefixis-,传入状态类型拼接,例如 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-plainis-activeis-disabledis-loadingis-roundis-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 传入不同的 colorbackground-colorborder-color 变量笼罩 default 默认按钮的字体色彩、背景色彩和边框色彩,并且定义了伪类 hoverfocusactivedisabled 交互状态,色彩变浅通过 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) 定义在同一个文件,伪类也是定义 colorbackground-colorborder-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,将 borderbackground 变通明,设置不同的字体色彩,定义伪类状态

  @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 类型、伪类状态还有奢侈按钮,通过批改 colorbackground-colorborder-color 笼罩默认款式,色彩变浅通过 mix 函数混合红色生成新的色彩,文本按钮、按钮组还有不同的按钮状态依据 bem 标准生成类款式定义。

通过学习优良的组件库设计,发现处处设计的很奇妙,站在伟人的肩膀上学习。

本文由mdnice多平台公布