簡介
終於遇到一個簡單的元件了,不過這個元件的實現還是和我之前的實現有所不同,下圖Element的Switch元件
看著就很簡單,是不是呀,官網程式碼點此之前自己的實現方式
關於開關元件,之前自己寫了一個,其實這個元件是不需要繫結任何click事件的,也就是說js部分幾乎可以不寫,核心思想就是利用<input type=checkbox>
的checked屬性,當滑鼠點選input時,會切換其checked屬性,這是原生checkbox的特性。下面是自己實現的switch的html
<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屬性就能辦到,如下
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實現滑塊的移動
Element的實現方式
先來看switch的html結構
<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這個方法就是切換開關的狀態
裡面先是一個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類的內容
@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如下
@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值