美文网首页web前端
自定义switch组件

自定义switch组件

作者: 姜治宇 | 来源:发表于2020-09-08 22:00 被阅读0次

开关在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>

相关文章

网友评论

    本文标题:自定义switch组件

    本文链接:https://www.haomeiwen.com/subject/zmvkektx.html