开关在html标签里是不存在的,但实际上开和关两种状态,跟checkbox的打对勾操作是一致的,我们将默认的checkbox样式修改掉即可。
这里还需注意一点:最外层的标签是用label还是div呢?
实际的checkbox是被隐藏掉了,点击的时候,如果是用div,则只会触发checkbox外面的感应部分;而用label的话,因为其for属性的作用,默认会绑定到隐藏的checkbox。
那答案是啥呢?
我们暂且先用label。
静态结构
components/myswitch.vue:
<template>
<label class="my-switch">
<input class="my-switch__input"
type="checkbox">
<span class="my-switch__core">
<span class="my-switch__button"></span>
</span>
</label>
</template>
<script>
export default {
name: 'MySwitch',
}
</script>
<style lang="scss">
.my-switch {
display: inline-flex;
align-items: center;
position: relative;
font-size: 14px;
line-height: 20px;
height: 20px;
vertical-align: middle;
.my-switch__input {
position: absolute;
width: 0;
height: 0;
opacity: 0;
margin: 0;
}
.my-switch__core {
margin: 0;
display: inline-block;
position: relative;
width: 40px;
height: 20px;
border: 1px solid #dcdfe6;
outline: none;
border-radius: 10px;
box-sizing: border-box;
background: #dcdfe6;
cursor: pointer;
transition: border-color .3s, background-color .3s;
vertical-align: middle;
.my-switch__button {
position: absolute;
top: 1px;
left: 1px;
border-radius: 100%;
transition: all .3s;
width: 16px;
height: 16px;
background-color: #fff;
}
}
}
.my-switch.is-checked {
.my-switch__core {
border-color: #409eff;
background-color: #409eff;
.my-switch__button {
transform: translateX(20px);
}
}
}
</style>
main.js:
import Vue from 'vue'
import App from './App.vue'
import MySwitch from './components/myswitch'
Vue.config.productionTip = false
Vue.component(MySwitch.name,MySwitch)
new Vue({
render: h => h(App),
}).$mount('#app')
App.vue:
<template>
<div id="app">
<my-switch v-model="isOpen"></my-switch>
</div>
</template>
<script>
export default {
data(){
return {
isOpen: false
}
}
}
</script>
功能实现
下面开始增加功能。
1、接收props
这里只需接收v-model的布尔型值value即可。
export default {
name: 'MySwitch',
props:{
value:{
type:Boolean,
default:false,
}
},
}
2、v-model的处理
根据单向数据流动规则,value是不能直接修改的。我们可以另外定义一个computed属性来做双向数据绑定。
<template>
<label class="my-switch" :class="{'is-checked': model}">
<input class="my-switch__input"
type="checkbox" :value="model" v-model="model">
<span class="my-switch__core">
<span class="my-switch__button"></span>
</span>
</label>
</template>
<script>
export default {
name: 'MySwitch',
props:{
value:{
type:Boolean,
default:false,
}
},
computed:{
model:{
get(){
return this.value
},
set(val){
console.log(val)
this.$emit('input',val)
}
}
}
}
</script>
<style lang="scss">
...
</style>
3、自定义背景色
开关的背景色会根据整体风格进行调整。我们在父组件可以传入active-color和inactive-color两个指令,分别代表开和关的背景色。
<template>
<div id="app">
<my-switch v-model="isOpen" active-color="#00ff00" inactive-color="#dcdfe6"></my-switch>
</div>
</template>
<script>
export default {
data(){
return {
isOpen: false
}
}
}
</script>
子组件接收到这两个颜色,需要更改一下原来class类的颜色,我们可以在标签上定义一个ref属性,然后用this.$refs.xxx来修改即可。
<template>
<label class="my-switch" :class="{'is-checked': model}">
<input class="my-switch__input"
type="checkbox" :value="model" v-model="model">
<span class="my-switch__core" ref="core">
<span class="my-switch__button"></span>
</span>
</label>
</template>
<script>
export default {
name: 'MySwitch',
props: {
value: {
type: Boolean,
default: false,
},
activeColor: {
type: String,
default: ''
},
inactiveColor: {
type: String,
default: ''
}
},
computed: {
model: {
get() {
return this.value
},
set(val) {
console.log(val)
this.$emit('input', val)
this.setColor(val)
}
}
},
mounted() {
this.setColor(this.value)
},
methods: {
setColor(val) {
if (this.activeColor || this.inactiveColor) {
let color = val ? this.activeColor : this.inactiveColor
this.$refs.core.style.borderColor = color
this.$refs.core.style.backgroundColor = color
}
}
}
}
</script>
<style lang="scss">
...
</style>
这里还有个有趣的问题。如果setColor函数不传形参,直接用this.value可以吗?也就是说:
<template>
<label class="my-switch" :class="{'is-checked': model}">
<input class="my-switch__input"
type="checkbox" :value="model" v-model="model">
<span class="my-switch__core" ref="core">
<span class="my-switch__button"></span>
</span>
</label>
</template>
<script>
export default {
name: 'MySwitch',
props: {
value: {
type: Boolean,
default: false,
},
activeColor: {
type: String,
default: ''
},
inactiveColor: {
type: String,
default: ''
}
},
computed: {
model: {
get() {
return this.value
},
set(val) {
console.log(val)
this.$emit('input', val)
this.setColor()
}
}
},
mounted() {
this.setColor()
},
methods: {
setColor() {
console.log(this.value)
if (this.activeColor || this.inactiveColor) {
let color = this.value ? this.activeColor : this.inactiveColor
this.$refs.core.style.borderColor = color
this.$refs.core.style.backgroundColor = color
}
}
}
}
</script>
<style lang="scss">
...
</style>
注意这样写是有问题的,通过打印value值发现,当点击激活按钮(触发set函数)时,this.value的值其实还是旧值false,因为通过$emit函数只是改变了父组件的值,子组件还未来得及改变。
因此,如果你要这么写的话,就得等待子组件视图也更新后才能得到最新值,我们可以用nextTick解决。
<template>
<label class="my-switch" :class="{'is-checked': model}">
<input class="my-switch__input"
type="checkbox" :value="model" v-model="model">
<span class="my-switch__core" ref="core">
<span class="my-switch__button"></span>
</span>
</label>
</template>
<script>
export default {
name: 'MySwitch',
props: {
value: {
type: Boolean,
default: false,
},
activeColor: {
type: String,
default: ''
},
inactiveColor: {
type: String,
default: ''
}
},
computed: {
model: {
get() {
return this.value
},
set(val) {
console.log(val)
this.$emit('input', val)
this.$nextTick(()=>{
this.setColor()
})
}
}
},
mounted() {
this.setColor()
},
methods: {
setColor() {
console.log(this.value)
if (this.activeColor || this.inactiveColor) {
let color = this.value ? this.activeColor : this.inactiveColor
this.$refs.core.style.borderColor = color
this.$refs.core.style.backgroundColor = color
}
}
}
}
</script>
<style lang="scss">
.my-switch {
display: inline-flex;
align-items: center;
position: relative;
font-size: 14px;
line-height: 20px;
height: 20px;
vertical-align: middle;
.my-switch__input {
position: absolute;
width: 0;
height: 0;
opacity: 0;
margin: 0;
}
.my-switch__core {
margin: 0;
display: inline-block;
position: relative;
width: 40px;
height: 20px;
border: 1px solid #dcdfe6;
outline: none;
border-radius: 10px;
box-sizing: border-box;
background: #dcdfe6;
cursor: pointer;
transition: border-color .3s, background-color .3s;
vertical-align: middle;
.my-switch__button {
position: absolute;
top: 1px;
left: 1px;
border-radius: 100%;
transition: all .3s;
width: 16px;
height: 16px;
background-color: #fff;
}
}
}
.my-switch.is-checked {
.my-switch__core {
border-color: #409eff;
background-color: #409eff;
.my-switch__button {
transform: translateX(20px);
}
}
}
</style>
网友评论