• element-ui 组件库 button 源码分析


    前言

    团队要根据新的 UI 规范实现一个组件库,button 组件规范要支持多种主题换肤,字体颜色、背景颜色、边框和禁用使用新的规范,并且一种主题颜色主要组件上使用两种主题颜色混合。另外,增加多一种幽灵按钮类型,经过分析,在 element-uibutton 组件上改造麻烦,不好维护,所以需要造一个 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>
    
        
        <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    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
        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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    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; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    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
    • 2
    • 3
    • 4

    1、bblock 的 mixin简写函数,调用 @include b(button) 参数 $block 赋值 button, 拼接命名空间变量 $namespace el 得到 el-button, !global 改为全局变量,可以给下文使用, @content 占位符插入内容

    @mixin b($block) {
      $B: $namespace+'-'+$block !global;
    
      .#{$B} {
        @content;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    使用 @mixin b 代码片段

    @include b(button) {
      display: inline-block;
      line-height: 1;
    }
    
    • 1
    • 2
    • 3
    • 4

    编译结果是

    .el-button {
      display: inline-block;
      line-height: 1;
    }
    
    • 1
    • 2
    • 3
    • 4

    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;
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    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;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用 m 函数

    @include b(button) {
      @include m(primary) {
      
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    编译结果是

    .el-button--primary {
      
    }
    
    • 1
    • 2
    • 3
    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;
        }
      }
      ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    when 函数 是定义不同状态的样式,$state-prefixis-,传入状态类型拼接,例如 when(loading).el-button.is-loading

    @mixin when($state) {
      @at-root {
        &.#{$state-prefix + $state} {
          @content;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    例如 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);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    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);
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    &.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%);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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;
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    按钮支持文本类型 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;
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    总结一下,button 按钮的样式变量定义在 commont/var.scss 维护,其他组件也是这种做法,这样就做到只需要修改 var.scss 文件就可以实现组件库的换肤。

    按钮的样式规范使用 bem 规范,@include b(button) 定义基础类样式 el-button;按钮大小是通过 @include button-size(...) 传入内边距、字体大小变量控制显示。

    不同的按钮 type 类型、伪类状态还有朴素按钮,通过修改 colorbackground-colorborder-color 覆盖默认样式,颜色变浅通过 mix 函数混合白色生成新的颜色,文本按钮、按钮组还有不同的按钮状态根据 bem 规范生成类样式定义。

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

  • 相关阅读:
    java学习的环境配置与安装(sublime汉化,jdk8,convert to utf8)快速入门
    BroadcastChannel学习笔记
    Scala基础入门
    数据库管理系统:Redis配置与使用
    WorkPlus即时通讯办公软件,助力企业实现移动化办公
    【音视频】MP4封装格式
    MVC、MVVM、MVP的区别
    计算机系统大作业:Hello的一生
    List容器(C++)
    MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作
  • 原文地址:https://blog.csdn.net/wexin_37276427/article/details/126658383