Element原始碼分析系列3-Button(按鈕)

超級索尼發表於2018-08-17

簡介

Button(按鈕)可能是最簡單的一個元件了(Icon元件表示不服,我TM連點選事件都沒),因為Button涉及到的東西真的不多,無非就是響應點選事件和具體的樣式編寫,最多加一個loading狀態禁止點選,下圖是Element的Button示意圖,官網程式碼點此

Element原始碼分析系列3-Button(按鈕)
主要就分為直角,圓角,圓形按鈕這幾種,這裡的按鈕顏色看著很舒服,比如危險按鈕的顏色不是#ff0000,而是有一點淡的紅色,視覺效果更柔和,可以借鑑這些顏色

Button原始碼分析

直接上程式碼,程式碼量不多

<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>
<script>
  export default {
    name: 'ElButton',
    inject: {
      elForm: {
        default: ''
      },
      elFormItem: {
        default: ''
      }
    },
    props: {
      type: {
        type: String,
        default: 'default'
      },
      size: String,
      icon: {
        type: String,
        default: ''
      },
      nativeType: {
        type: String,
        default: 'button'
      },
      loading: Boolean,
      disabled: Boolean,
      plain: Boolean,
      autofocus: Boolean,
      round: Boolean,
      circle: Boolean
    },
    computed: {
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      buttonSize() {
        return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
      },
      buttonDisabled() {
        return this.disabled || (this.elForm || {}).disabled;
      }
    },
    methods: {
      handleClick(evt) {
        this.$emit('click', evt);
      }
    }
  };
</script>
複製程式碼

我們一步步分析,首先很明確,<el-button>就是封裝了原生的button而已,原生button有個地方需要注意,請始終為 <button> 元素規定 type 屬性。不同的瀏覽器對 <button> 元素的 type 屬性使用不同的預設值,值有3種,button,submit,reset分別表示按鈕,提交,重置。而且如果在 HTML 表單中使用 <button> 元素,不同的瀏覽器可能會提交不同的按鈕值。請使用 <input> 在 HTML 表單中建立按鈕

首先我們來看第一句class="el-button",這裡宣告瞭按鈕的基本類,也就是所有按鈕通用的樣式,見下圖

Element原始碼分析系列3-Button(按鈕)
可以看到其中有幾個樣式比較重要,首先是display:inline-block,表明按鈕內聯塊狀分佈,一行可以有多個排列,且可以控制寬高,-webkit-appearence:none表示將按鈕原生的樣式去掉,首先是太醜其實不同瀏覽器表現不同,注意這裡的按鈕沒有設定寬高,寬高都是由字型大小撐開來的,然後設定padding來產生內間距,line-height:1表示行高和字型大小一樣

然後第二句@click="handleClick"這個是重點,這表明給原生button繫結了點選事件,我們使用該元件時是按下圖的方式

Element原始碼分析系列3-Button(按鈕)
這裡需要手動寫一個@click,然後我們看一下handleClick的程式碼

methods: {
      handleClick(evt) {
        this.$emit('click', evt);
      }
}
複製程式碼

這裡實際上是用Vue的內建事件系統向父級觸發了一個click事件,父級就是<el-button>,因為實際點選我們是點選在原生button上的,而使用元件時卻是在<el-button>上監聽事件,所以這裡需要將click事件派發到父級,父級監聽這個click事件,第二個引數是事件物件。如果不寫這個handleClick將不會觸發任何點選效果

第三句:disabled="buttonDisabled || loading"表示控制按鈕的禁用狀態,注意disabled是原生的屬性,當其為true時,按鈕無法被點選,且滑鼠hover的樣式不是小手,這裡禁用狀態是由loading狀態和使用者自定義的禁用與否共同決定,其中使用者定義的disabled會被轉化為一個計算屬性,如下,後面的項表示表單相關的內容,此處不詳細解釋

禁用狀態是由2部分組成:一是功能禁用,通過原生的disabled屬性來實現,二是樣式上禁用,通過將按鈕置灰和cursor:not-alowed將滑鼠展示為一個禁止的標誌來實現

buttonDisabled() {
        return this.disabled || (this.elForm || {}).disabled;
 }
複製程式碼

再來看:autofocus="autofocus"這句話,如果為true表示按鈕自動獲得焦點,按鈕獲得焦點就是按鈕為focus的狀態,可以用於提示使用者
:type="nativeType"表示按鈕的原生型別,button ,reset還是submit,接下來是控制按鈕不同種類的class

:class="[
      type ? 'el-button--' + type : '',
      buttonSize ? 'el-button--' + buttonSize : '',
      {
        'is-disabled': buttonDisabled,
        'is-loading': loading,
        'is-plain': plain,
        'is-round': round,
        'is-circle': circle
      }
    ]"
複製程式碼

這裡注意繫結的class是一個陣列,裡面又混合了物件的形式,這種寫法可以借鑑,物件裡面的類控制了該按鈕是否是禁用,loading,樸素,圓角,圓形,然後是這3句話

<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>
複製程式碼

這3句就是<button></button>中間的內容了,第一個i標籤表示載入狀態下的loading圖案,可以看出這個圖案就是一個字型圖示而已,在不斷的旋轉(animation:rotating 2s linear infinite)

Element原始碼分析系列3-Button(按鈕)

Element原始碼分析系列3-Button(按鈕)

第二個i標籤是按鈕的圖示,使用者設定,此圖示在loading狀態下隱藏,然後是<span>這個標籤當且僅當<el-button>中有內容時才存在,內容放在slot插槽中,$slots.default來判斷是否有子元素存在,可以借鑑

後面的inject估計是和表單相關,這裡不做介紹,注意一下當按鈕被點選時,樣式也會發生變化,下圖中成功按鈕被點選變成深色,這是通過css的:active偽類來實現,我之前一直以為只有a標籤有active偽類,原來button也可以用啊

Element原始碼分析系列3-Button(按鈕)

下面講講按鈕的大小控制,其實很簡單,

Element原始碼分析系列3-Button(按鈕)
size屬性在通過繫結class的這句話buttonSize ? 'el-button--' + buttonSize : ''將size轉化為類名,從而使用不同的尺寸類來控制大小,buttonSize是個計算屬性通過size得到

綜上,我們明白了元件的各種屬性最終會轉化為對應的class來控制按鈕的樣式表現,就是在:class裡拼接字串而已

ButtonGroup原始碼分析

ButtonGroup就是按鈕組,將多個按鈕組合在一起,如下圖

Element原始碼分析系列3-Button(按鈕)
其原始碼就更簡單了,就是封裝了一個div,注意el-button-group的內容是display:inline-block,也就說明這個wrapper其實也是內聯塊狀的表現,裡面的slot承載子button

<template>
  <div class="el-button-group">
    <slot></slot>
  </div>
</template>
<script>
  export default {
    name: 'ElButtonGroup'
  };
</script>
複製程式碼

這裡重點在按鈕之間的白色間隙是怎麼實現的,答案就是css偽類:last-child:first-child:not:first-child 選擇器用於選取屬於其父元素的首個子元素的指定選擇器,也就是說如果多個相同層級的p標籤排在一起,他們的父元素是div,那麼p:first-child就會選中第一個p標籤
下面上scss程式碼

@each $type in (primary, success, warning, danger, info) {
    .el-button--#{$type} {
      &:first-child {
        border-right-color: rgba($--color-white, 0.5);
      }
      &:last-child {
        border-left-color: rgba($--color-white, 0.5);
      }
      &:not(:first-child):not(:last-child) {
        border-left-color: rgba($--color-white, 0.5);
        border-right-color: rgba($--color-white, 0.5);
      }
    }
  }
複製程式碼

很明顯,這裡設定了第一個元素的右側border和最後一個的左側border,對於既不是第一個也不是最後一個的元素,設定雙側border,注意這裡只設定了border的顏色,其實所有按鈕預設都有border,顏色和按鈕主體顏色一致,所以這裡只修改了顏色就能達到目的

相關文章