Element源碼分析系列9-Switch(開關)

簡介

終於遇到一個簡單的組件了,不過這個組件的實現仍是和我以前的實現有所不一樣,下圖Element的Switch組件css

看着就很簡單,是否是呀,官網代碼 點此

以前本身的實現方式

關於開關組件,以前本身寫了一個,其實這個組件是不須要綁定任何click事件的,也就是說js部分幾乎能夠不寫,核心思想就是利用<input type=checkbox>的checked屬性,當鼠標點擊input時,會切換其checked屬性,這是原生checkbox的特性。下面是本身實現的switch的htmlhtml

<template>
    <!--點擊外層label,內層checkbox會發生改變-->
    <label class="switch">
      <input type="checkbox" v-model="value" />
      <!--內層滑動條,圓形按鈕是after元素-->
      <div class="switch-checkbox"></div>
    </label>
</template>
複製代碼

裏面的input用display:none隱藏掉,<div class="switch-checkbox"></div>纔是滑塊的背景,用:after僞元素來模擬裏面的圓形滑塊,關鍵是在不寫js的狀況下如何控制滑塊的左右移動,經過checked屬性就能辦到,以下vue

input[type="checkbox"] {
      //隱藏input
      display: none;
      //利用input的checked觸發滑動動畫
      &:checked {
        //這裏的+(相鄰兄弟選擇器)很重要,不然沒法選擇到,由於不加+就變成子元素
       +.switch-checkbox {
          background-color:@activeBgColor;
          &:after {
            transform: translateX(26px);
            background: @activeButtonColor;
            opacity: 1!important;
          }
        }
      }
    }
複製代碼

&:checked狀況下用相鄰兄弟選擇器選擇.switch-checkbox類裏面的after僞元素,讓其的transform發生改變,從而更改滑塊的位置,這樣就不用寫一行js實現滑塊的移動git

Element的實現方式

先來看switch的html結構github

<template>
  <div
    class="el-switch"
    :class="{ 'is-disabled': switchDisabled, 'is-checked': checked }"
    role="switch"
    :aria-checked="checked"
    :aria-disabled="switchDisabled"
    @click="switchValue"
  >
    <input
      class="el-switch__input"
      type="checkbox"
      @change="handleChange"
      ref="input"
      :id="id"
      :name="name"
      :true-value="activeValue"
      :false-value="inactiveValue"
      :disabled="switchDisabled"
      @keydown.enter="switchValue"
    >

    <!--前面的文字描述-->
    <span
      :class="['el-switch__label', 'el-switch__label--left', !checked ? 'is-active' : '']"
      v-if="inactiveIconClass || inactiveText">
      <i :class="[inactiveIconClass]" v-if="inactiveIconClass"></i>
      <span v-if="!inactiveIconClass && inactiveText" :aria-hidden="checked">{{ inactiveText }}</span>
    </span>

    <!--開關核心部分-->
    <span class="el-switch__core" ref="core" :style="{ 'width': coreWidth + 'px' }">
    </span>

    <!--後面的文字描述-->
    <span
      :class="['el-switch__label', 'el-switch__label--right', checked ? 'is-active' : '']"
      v-if="activeIconClass || activeText">
      <i :class="[activeIconClass]" v-if="activeIconClass"></i>
      <span v-if="!activeIconClass && activeText" :aria-hidden="!checked">{{ activeText }}</span>
    </span>
  </div>
</template>
複製代碼

最外層一個div包裹,這是爲了當有文字描述時,能夠點擊文字也觸發開關狀態改變,注意這個div上綁定了點擊事件@click="switchValue",這就和本身實現的方式不一樣了,Element的開關組件寫了不少js,目的是能更好的控制一些特性實現,功能更豐富,能夠猜到,switchValue這個方法就是切換開關的狀態bash

裏面先是一個input,這個input被隱藏掉,css代碼以下函數

@include e(input) {
    position: absolute;
    width: 0;
    height: 0;
    opacity: 0;
    margin: 0;
    &:focus ~ .el-switch__core {
      outline: 1px solid $--color-primary;
    }
  }
複製代碼

絕對定位且寬高都爲0,也就是說沒法點擊到該input,而後是3個span並排下來,第一個和最後一個span都是文字描述,若是用戶傳入文字才顯示,不然不顯示,中間的span纔是核心,很明顯這個span是開關的外層橢圓背景,裏面的滑塊是由after僞元素實現動畫

<span class="el-switch__core" ref="core" :style="{ 'width': coreWidth + 'px' }">
</span>
複製代碼

看一下el-switch__core類的內容ui

@include e(core) {
    margin: 0;
    display: inline-block;
    position: relative;
    width: $--switch-width;
    height: $--switch-height;
    border: 1px solid $--switch-off-color;
    outline: none;
    border-radius: $--switch-core-border-radius;
    box-sizing: border-box;
    background: $--switch-off-color;
    cursor: pointer;
    transition: border-color .3s, background-color .3s;
    vertical-align: middle;

    &:after {
      content: "";
      position: absolute;
      top: 1px;
      left: 1px;
      border-radius: $--border-radius-circle;
      transition: all .3s;
      width: $--switch-button-size;
      height: $--switch-button-size;
      background-color: $--color-white;
    }
  }
複製代碼

開關外層的橢圓背景是display:inline-block且相對定位,由於裏面的滑塊要絕對定位,:after部分就是一個絕對定位的圓形,transition: all .3s規定了滑塊動畫時間以及背景顏色變化的時間,可是換這個&:after只是滑塊未激活狀態,激活狀態的css以下this

@include when(checked) {
    .el-switch__core {
      border-color: $--switch-on-color;
      background-color: $--switch-on-color;
      &::after {
        left: 100%;
        margin-left: -$--switch-button-size - 1px;
      }
    }
  }

複製代碼

能夠看到激活狀態下滑塊的left值變爲100%,至關於移動到了右側,而外層橢圓形背景的顏色也變化爲滑塊激活時的顏色

Switch的js邏輯

如今介紹下整個組件的數據傳遞邏輯,首先先來看用法

<el-switch
  v-model="value2"
  active-color="#13ce66"
  inactive-color="#ff4949">
</el-switch>
複製代碼

只須要給該組件的v-model設置一個data中的值便可,當開關開啓關閉後整個value2會相應的變化,首先要知道組件的v-model用法,v-model就是@input和:value的簡寫,所以在組件內部要有一個value做爲prop,具體看Vue官網。而後回到Switch,最外層的div綁定了click事件,代碼以下

switchValue() {
   !this.switchDisabled && this.handleChange();
},
複製代碼

當組件不在禁用狀態下時觸發handleChange方法,handleChange以下

handleChange(event) {
    this.$emit('input', !this.checked ? this.activeValue : this.inactiveValue);
    this.$emit('change', !this.checked ? this.activeValue : this.inactiveValue);
    this.$nextTick(() => {
      // set input's checked property // in case parent refuses to change component's value
      this.$refs.input.checked = this.checked;
    });
  },
複製代碼

這裏主要看前2句,第一句是emit了一個input,同時將開關最新的值傳遞出去,這就是組件v-model的用法,必須指定一個$emit('input')來改變組件上v-model的值,不然沒法更新用戶傳入的v-model,而後第二個$emit是組件添加的change事件,用於switch 狀態發生變化時的回調函數,用戶能夠在這裏面監測開關值改變了這一事件

!this.checked ? this.activeValue : this.inactiveValue說明了若是不是激活狀態,則傳遞出去activeValue,激活狀態的值,這就是在切換狀態了。那麼this.checked是啥呢?來看一下

computed: {
      checked() {
        return this.value === this.activeValue;
      },
}
複製代碼

原來是一個計算屬性,當v-model的值和激活狀態的值相同時就是checked狀態,反之不是,這樣當this.$emit('input', !this.checked ? this.activeValue : this.inactiveValue)後checked這個計算屬性也就跟着變化了,那麼問題來了,handleClick後是如何控制滑塊的動畫效果呢?由於這裏沒有寫任何js,

這裏經過$refs.input.checked拿到了內置input的checked的值(這裏經過setAttribute也能夠改,),注意到最外層的div

<div
    ...
    :class="{ 'is-disabled': switchDisabled, 'is-checked': checked }"
    @click="switchValue"
  >
複製代碼

這裏的class內有個is-checked類,它就是由checked這個計算屬性控制,當checked爲true時,div就添加這個is-checked類,這個類實際上啥都沒有,做用是用來控制div裏面滑塊span的類以及after僞元素,以下

當有is-checked類時,上述css就被激活,所以改變了滑塊背景的背景色和邊框色,同時也改變了:after僞元素。

handleClick裏面的nextTick那裏不明白,有這麼2句註釋,這裏將input的checked屬性也改變了,是爲了防止父組件拒絕修改value,爲何會拒絕修改呢,不太清楚

// set input's checked property // in case parent refuses to change component's value
複製代碼

我試着將switch組件裏面的全部input相關的內容都去掉,該組件仍然工做正常,說明input不是必須的,仔細想一下也對,上面的分析和input沒有任何關係,組件內維護了activeValue和inactiveValue,經過v-model傳入value,而後將value和activeValue相比來肯定switch是否checked,確實不須要input

最後看一下created裏面的內容

created() {
      if (!~[this.activeValue, this.inactiveValue].indexOf(this.value)) {
        this.$emit('input', this.inactiveValue);
      }
    },
複製代碼

這裏說明當用戶傳入的v-model的值既不是activeValue也不是inactiveValue時,將inactiveValue傳遞出去,也就是讓組件置位非開啓狀態,這個~表明按位非運算符,若是[this.activeValue, this.inactiveValue].indexOf(this.value)爲-1,則按位非後變爲0,再!後變爲1,爲true,則進if

再說下~~和!!,前者是用來將小數向下取整的操做(~對浮點數進行了截斷),後者是用來將值變爲bool值

相關文章
相關標籤/搜索