内容简介:终于遇到一个简单的组件了,不过这个组件的实现还是和我之前的实现有所不同,下图Element的Switch组件关于开关组件,之前自己写了一个,其实这个组件是不需要绑定任何click事件的,也就是说js部分几乎可以不写,核心思想就是里面的input用display:none隐藏掉,
终于遇到一个简单的组件了,不过这个组件的实现还是和我之前的实现有所不同,下图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值
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。