实现一个可调节大小的 Switch 开关

switch 项目中基本都会用到,各大组件库都有实现这个基本的组件,不过有时候可能ui要求和各组件库差异较大,这时候就只能手动实现了。

1、老规矩先看效果

在这里插入图片描述

2、思路分析

笔者也查看了其他组件库,有部分是通过原生组件(input type=“checkbox”)进行改造,大多数还是直接通过写div来实现。

自己实现ui层次可分为两部分:
1、外层一个标签实现 switch 盒子
2、内存通过一个标签实现中间动的小球

    <div  class="switch" >
        <div class="ball"></div>
    </div>

其他就是一些 css 问题(背景颜色、中间小球颜色)

3、实现开、关过程

上面我们实现了基本样式,接下来就结合实际实现逻辑。
1、点击时,状态开或关,此时小球移动到左边或者右边

实现小球的移动不难,直接使用 css 定位即可(left:0或者rigth:0,笔者这里采用 translateX 实现 ,使用translateX 的话,建议使用css calc 函数,便于计算位移的距离(switch的宽度-小球的宽度));这里比较简单就省略代码演示

4、优化

直接设置css,切换的太过生硬了,于是我们加上css过渡效果

// 背景过渡
transition: background-color 0.2s ease;
// 小球过渡
transition: transform 0.2s ease-in-out;

有时候,我们可能想扩展下,不仅仅是开关状态,可能还需要添加文本
在这里插入图片描述
我们只需要简单扩展下这个组件就可以了(不过,建议字数2个文本为好,因为要设置文字的移动动画效果,就得计算宽度问题,或者实际项目中,ui大小基本会统一,不会随意变动,可以考虑写死)

props: {
    
    
    hasText: {
    
    
        type: Boolean,
        default: false
    },
    // 最好使用2个汉字
    statusText: {
    
    
        type: Object,
        default: () => ( 
            {
    
    
                open: '开启',
                close: '关闭'
            } 
        )
    },
  }
<div class="switch" >
   <span   
        v-if="hasText" 
        class="switch-text" >
        {
   
   {stastus ? statusText.open : statusText.close}}
    </span>
    <div class="ball" ></div>
</div>

5、改变大小

想要实现大小可以随意切换,同时避免宽度 js 计算问题(大小变了,小球、文字位移距离肯定也就变了),笔者这里采用 css em单位(相对当前文本字体大小的单位,假设 1em = 10px,则2em = 20px)

/* 
        默认宽度80px 高24px
        设置 1em = 10px

        则 80px = 8em
           24px = 2.4em
    */
    .switch{
    
    
        width: 8em;
        height: 2.4em;
        background: #00C5AB;
        border-radius: 1.2em;
        cursor: pointer;
        position: relative;
        transition: background-color 0.2s ease;
        color: #fff;
    }

注意: em 和 px 的换算规则需要结合自己实际情况调整,笔者这里是根据自己需求设计的,所以,我们可以通过 props 传递单位大小,进而实现调整 switch 的大小,避免位移计算
在这里插入图片描述

6、事件处理相关这里就不啰嗦了

7、完整代码如下—仅供参考。可根据实际修改

<template>
    <div 
        class="switch" 
        @click="onTabSwitch" 
        :class="[ switchClass.switch ]"
        :style="switchStyle">
        <span   
            v-if="hasText" 
            class="switch-text" 
            :class="[ switchClass.text ]">
            {
    
    {
    
    stastus ? statusText.open : statusText.close}}
        </span>
        <div class="ball" :class="[ switchClass.ball ]"></div>
    </div>
</template>

<script>
    // ex: <VSwitch v-model="isOpen" @change="changeSwitch" />
    export default {
    
    
        name: 'VSwitch',
        model: {
    
    
            prop: 'value',
            event: 'update'
        },
        props: {
    
    
            value: {
    
    
                type: Boolean,
                default: false
            },
            hasText: {
    
    
                type: Boolean,
                default: false
            },
            // 最好使用2个汉字
            statusText: {
    
    
                type: Object,
                default: () => ( 
                    {
    
    
                        open: '开启',
                        close: '关闭'
                    } 
                )
            },
            // 调整大小时,最好不要汉字,因为此时需要动态计算汉字span的宽度,以设置 left 距离(为了添加动画)
            size: {
    
    
                type: String,
                default: '10px'
            },
            disable: {
    
    
                type: Boolean,
                default: false
            }
        },
        data() {
    
    
            return {
    
    
                stastus: false
            }
        },
        watch: {
    
    
            value(val) {
    
    
                if(this.disable) {
    
    
                    return 
                }
                this.stastus = Boolean(val)
            }
        },
        computed: {
    
    
            switchClass({
     
      stastus }) {
    
    
                let state = stastus ? 'on' : 'off'
                return {
    
    
                    switch: `switch-${
      
      state}`,
                    text: `switch-text-${
      
      state}`,
                    ball: `ball-${
      
      state}`
                }
            },
            switchStyle({
     
      size,disable }) {
    
    
                return {
    
    
                    fontSize: size,
                    cursor: disable ? 'not-allowed' : 'pointer',
                    opacity: disable ? '.6' : ''
                }
            }
        },
        mounted() {
    
    
            if(this.value) {
    
    
                this.stastus = Boolean(this.value)
            }
        },
        methods: {
    
    
            onTabSwitch(e) {
    
    
                // 可补充一个点击前回调
                this.$emit('click',e)
                if(this.disable) {
    
    
                    return 
                }
                this.stastus = !this.stastus
                this.$emit('change',this.stastus)
                this.$emit('update',this.stastus)
            }
        }
    }
</script>

<style scoped>
    /* 
        默认宽度80px 高24px
        设置 1em = 10px

        则 80px = 8em
           24px = 2.4em
    */
    .switch{
    
    
        width: 8em;
        height: 2.4em;
        background: #00C5AB;
        border-radius: 1.2em;
        cursor: pointer;
        position: relative;
        transition: background-color 0.2s ease;
        color: #fff;
    }
    .ball{
    
    
        font-size: inherit;
        width: 2.2em;
        height: 2.2em;
        background: #FFFFFF;
        border-radius: 50%;
        position: absolute;
        top: 0.1em;
        left: 0.1em;
        transition: transform 0.2s ease-in-out;
    }
    .switch-text{
    
    
        font-size: 14px;
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        transition: left 0.2s ease-in-out;
    }
    .switch-text-on{
    
    
        left: 1.2em;
        /* left: 0.6em; */
    }
    .switch-text-off{
    
    
        left: 2.2em;
        /* right: 0.6em; */
    }
    .ball-on{
    
    
        transform: translateX(calc( 8em - 2.4em));
    }
    .switch-off{
    
    
        background-color: #C0C4CC;
    }
</style>

猜你喜欢

转载自blog.csdn.net/qq_45219069/article/details/125848362