ifqlOqaxH

23朵毒蘑菇

vue滑动切换tab组件,鼠标按住滑动切换,不到200行代码,超级轻量。

原文章迁移

vue

2021-08-05 14:41:56 已有版本 1 个 show:0.52kTYPE: blog

毒蘑菇搜索

因为自己的小站首页需要一个可以滑动切换的显示内容的功能,本来是用的swiper,但是坑,BUG实在是太多了,实在是搞不明白了,就这么个小功能还是决定自己写一个组件。

直接贴代码,基本都含有注释

<template>
<div 
    class="touch_tab_container"
    ref="TabContainerEl"
    :style="{
        height:height+'px',
    }"
    @touchmove="(e)=>{
        mouseMove(e.touches[0]);
    }"
    @touchstart="(e)=>{
        onMouseDown(e.touches[0]);
    }"
    @touchend="(e)=>{
        onMouseUp(e.touches[0]);
    }"
    @mousemove="mouseMove"
    @mouseleave="onMouseLeave"
    @mousedown="onMouseDown"
    @mouseup="onMouseUp">
    <div 
        :class="{
            container:true,
            animation:animation,
        }"
        ref="ContainerEl"
        :style="{
            transform:`translateX(${translateX}px)`,
        }">
        <slot></slot>
    </div>
</div>
</template>
<script>
//自定义touchTab组件
export default {
    name:'TouchTab',
    data() {
        return {
            height:0,  //总容器高度
            TabContainerEl:undefined,  //总容器元素
            ContainerEl:undefined,
            tabItemEls:[],
            mouseDown:false,  //鼠标是否按下
            translateX:0,  //容器的偏移量
            animation:false,  //是否开启容器动画
            activeIndex:0,
            speed:0,  //移动速度
        };
    },
    computed:{
        translateX_:{  //translateX的代理层
            set(newValue){
                if(!this.ContainerEl) return;
                if(newValue > 0) return;
                if(newValue < this.TabContainerEl.clientWidth-this.ContainerEl.clientWidth) return;
                this.translateX = newValue;
                if(this.mouseDown){  //如果鼠标按下的话计算移动速率
                    let oldTranslateX = this.oldTranslateX || newValue;
                    let oldDate = this.oldDate || new Date().getTime();
                    let newTranslateX = newValue;
                    let newDate =new Date().getTime();
                    this.speed =  Math.abs((newTranslateX - oldTranslateX)/(newDate - oldDate));
                    this.oldTranslateX = newTranslateX;
                    this.oldDate = newDate;
                }
            },
            get(){
                return this.translateX;
            },
        },
    },
    watch:{
        activeIndex:{  //当前选项发生变化时向外部暴露当前显示的选项索引
            handler(newVlaue){
                this.$emit('onChange',newVlaue);
            },
            immediate:true,
        },
    },
    mounted(){
        this.TabContainerEl = this.$refs.TabContainerEl;
        this.ContainerEl = this.$refs.ContainerEl;
        this.tabItemEls = this.$refs.ContainerEl.children;
        this.setActiveEl(this.activeIndex);  //设置初始显示item
        const that = this;
        requestAnimationFrame(function func(){  //每一帧都计算样式
            that.computeStyle();
            requestAnimationFrame(func);
        });
    },
    methods:{
        mouseMove(e){  //鼠标移动事件
            if(!this.mouseDown) return;  //必须是鼠标按下
            this.animation = false;
            let clientX = e.clientX - this.lastEnven.clientX;
            this.translateX_ = this.translateX_ + clientX;
            this.lastEnven = e;
        },
        onMouseLeave(){  //鼠标移出事件
            this.mouseDown = false;
            this.lastEnven = undefined;
            this.computeIndex();
        },
        onMouseDown(e){  //鼠标按下事件
            this.mouseDown = true;
            this.lastEnven = e;
        },
        onMouseUp(){  //鼠标抬起事件
            this.mouseDown = false;
            this.lastEnven = undefined;
            this.computeIndex();
        },
        computeStyle(){  //计算样式
            if(this.tabItemEls.length>0){
                this.tabItemEls.forEach(el => {  //设置子元素的宽度与总容器宽度一致
                    el.style.width = this.TabContainerEl.clientWidth+'px';
                    el.style.minWidth = this.TabContainerEl.clientWidth+'px';
                });
                this.height = this.tabItemEls[this.activeIndex].clientHeight;
            }
        },
        computeIndex(){  //计算索引位置
            let itemWidth = this.TabContainerEl.clientWidth;
            let translateX = Math.abs(this.translateX_);
            let width_ = itemWidth*this.activeIndex - translateX;
            let direction = width_>0?'left':'right';
            width_ = Math.abs(width_);
            if(width_>(itemWidth/2)){  //如果滑动超过了一半
                this.setActiveEl(
                    direction=='right'?this.activeIndex+1:this.activeIndex-1,
                );
            }else if(this.speed>0.5){  //表示没有超过一半,可以通过滑动速率和方向判断
                this.setActiveEl(
                    direction=='right'?this.activeIndex+1:this.activeIndex-1,
                );
            }else{
                this.setActiveEl(this.activeIndex);
            }
            this.speed = 0;
        },
        setActiveEl(index){  //设置当前显示的item(可外部调用)
            this.animation = true;
            if(index<=0){
                this.activeIndex = 0;
            }else if(index>(this.tabItemEls.length-1)){
                this.activeIndex = this.tabItemEls.length-1;
            }else{
                this.activeIndex = index;
            }
            this.translateX_ = -this.TabContainerEl.clientWidth * this.activeIndex;
        },
    },
}
</script>
<style lang="scss" scoped>
.touch_tab_container{
    width: 100%;
    margin: 0 auto;
    background-color: aquamarine;
    border-radius: 5px;
    height: fit-content;
    overflow: hidden;
    transition: height 0.2s;
    >.container{
        -webkit-touch-callout: none;
        -webkit-user-select: none;
        -khtml-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
        width:fit-content;
        display: flex;
        transform: translateX(0px);
        >.item{
            min-width: 0;
            width: 0;
            height: fit-content;
        }
    }
    .animation{
        transition: all 0.2s;
        transition-timing-function: cubic-bezier(0.69, 0.05, 0.24, 1.01);
    }
}
</style>

使用方法

<myComponent
    ref="myComponent"
    @onChange="onChange">
</myComponent>

//直接设置显示那个选项
this.$refs.myComponent.setActiveEl(1);

示例,首页这里的滑动翻页就是这种形式