tab栏切换动画实现及注意事项

柳逸春
2023-12-01

tab栏切换动画实现及注意事项

技术栈:vue

  1. 遇到的问题

scene : 移动端页面左右滑动切换tab栏,实现页面切换类 app 原生动画,不影响页面正常点击事件

key : 移动端左右滑动,可以从touchstart , touchmove , touchend 事件入手,获取 pageX 左右移动的距离来判断方向,pageY 的上下移动距离来确定是否是滚动事件,监听 touchtrigger event 来判断是否是 tap 事件,从而实现功能

// template
<div @touchstart.stop = "touchStart" @touchmove.stop = "touchMove"  @touchend.stop = "touchEnd" class="touch-box">
</div>

// methods
touchStart(e) {
    const touch = e.touches[0]
    this.touch.startX = touch.pageX
    this.touch.startY = touch.pageY
    this.date_start = new Date().getTime();
},
touchEnd(e) {
    let touch = e.changedTouches[0]
    let date_end = new Date().getTime();
    let dur = date_end - this.date_start;
    let startX = this.touch.startX
    let startY = this.touch.startY
    let disX = touch.pageX - startX
    let disY = touch.pageY - startY   
    // 判断是滑动还是点击事件
    // 距离和时间可以自主调节
    if((Math.abs(disX)>10 && Math.abs(disX)< 130) || Math.abs(disY)>100){
        if(Math.abs(disX) > 5*Math.abs(disY)  && dur < 200){
            let slectTabArr = this.slectTabArr
            let number = 0 
            let selectTabFlag = getStorage('selectTabFlag')
            slectTabArr.filter((item,index)=>{
                if(selectTabFlag === item){
                    number = index
                }
            })
            if(disX < -10){
                // 向左滑动则向右选中列表中的数据切换
            }
            if(disX >10){
                // 向右滑动则向左选中列表中的数据切换
            }
        } else {
            return false
        }
    } else {
        return false
    }
},

注意:这里不能用 e.preventdefault 会发现默认事件都禁止那么滑动事件和点击事件也都禁止了。

  1. 遇到的问题

scene : **真机测试iosandroid 的触发机制并不一样,在android中无法主动触发 touchend 事件 **

key : 这里主要去排查了一下,android是通过禁止 touchmovee.preventdefault 默认事件 去触发后面的 touchend 事件,所以兼容 iosAndroid

touchMove(e){
    let u = navigator.userAgent
    if(u.indexOf('Android') > -1 || u.indexOf('Linux') >-1 ){
        let date_end = new Date().getTime();
        let dur = date_end - this.date_start;
        let touch = e.touches[0]
        let startX = this.touch.startX
        let startY = this.touch.startY
        let disX = touch.pageX - startX
        let disY = touch.pageY - startY 
        if (Math.abs(disX)>10 && Math.abs(disY) < 20 && dur<200) {
            //表示是滚动完成后的那个touch不触发/间隔太短
            e.preventDefault(); 
        }
    } else {
        return false
    }
},
    
touchEnd(e) {
    let touch = e.changedTouches[0]
    let date_end = new Date().getTime();
    let dur = date_end - this.date_start;
    let startX = this.touch.startX
    let startY = this.touch.startY
    let disX = touch.pageX - startX
    let disY = touch.pageY - startY   
    // 判断是滑动还是点击事件
    let u = navigator.userAgent
    if( u.indexOf('iPhone') > -1){
        if((Math.abs(disX)>10 && Math.abs(disX)< 130) || Math.abs(disY)>100){
            if(Math.abs(disX) > 5*Math.abs(disY)  && dur < 200){
                let slectTabArr = this.slectTabArr || []
                let number = 0 
                let selectTabFlag = getStorage('selectTabFlag')
                slectTabArr.filter((item,index)=>{
                    if(selectTabFlag === item){
                        number = index
                    }
                })
                if(disX < -10){
                    // 向左滑动则向右选中列表中的数据切换
                    number = (number >= slectTabArr.length-1) ? 0 : ++number
                    selectTabFlag = slectTabArr[number]
                    // 点击切换切换到选中tab栏
                    this.selectTabBar(selectTabFlag)
                    setStorage("selectTabFlag",selectTabFlag)
                }
                if(disX >10){
                    // 向右滑动则向左选中列表中的数据切换
                    number = number <= 0 ? slectTabArr.length-1 : --number
                    selectTabFlag = slectTabArr[number]
                    // 点击切换切换到选中tab栏
                    this.selectTabBar(selectTabFlag)
                    setStorage("selectTabFlag",selectTabFlag)
                }
            }else {
                return false
            }
        } else {
            return false
        }
    } else if(u.indexOf('Android') > -1 || u.indexOf('Linux') > -1){
        if(Math.abs(disX)>10 || Math.abs(disY)>10){
            if(Math.abs(disX) > 5*Math.abs(disY) && dur < 200){
                let slectTabArr = this.slectTabArr || []
                let number = 0 
                let selectTabFlag = getStorage('selectTabFlag')
                slectTabArr.filter((item,index)=>{
                    if(selectTabFlag === item){
                        number = index
                    }
                })
                if(disX < -10){
                    // 向左滑动则向右选中列表中的数据切换
                    number = number >= slectTabArr.length-1 ? 0 : ++number
                    selectTabFlag = slectTabArr[number]
                    // 点击切换切换到选中tab栏
                    this.selectTabBar(selectTabFlag)
                    setStorage("selectTabFlag",selectTabFlag)
                }
                if(disX >10){
                    // 向右滑动则向左选中列表中的数据切换
                    number = number <= 0 ? slectTabArr.length-1 : --number
                    selectTabFlag = slectTabArr[number]
                    // 点击切换切换到选中tab栏
                    this.selectTabBar(selectTabFlag)
                    setStorage("selectTabFlag",selectTabFlag)
                }
            }else {
                return false
            }
        } else {
            return false
        }
    }
},
  1. 遇到的问题

scene : tab栏动画实现

key : 这里遇到一个选择,应该是用transition-group 还是使用class类定义左右不同的动画,应为tab栏本身的切换会有对应的active样式,且不需要显示隐藏,所以选择了加class类来实现

<ul  class="company-tab-bar" >
    <li class="company-tab-item" 
    v-for="(item,index) in slectTabArr" 
    :key = " `current${index}` " 
    :class="[{'active':selectTabFlag=== item },slideDir]"
    @click="selectTabBar(item)">
        {{slectTabObj[item]}}
    </li>
</ul>

// style 
.company-tab-item {
    display: inline-block;
    margin-right: 28/@r;
    position: relative;
    &.active {
        color: @bgheader;
        &::before {
            content: "";
            width: 100%;
            height: 2/@r;
            position: absolute;
            bottom: 0;
            background-color: @bgheader;
            border-radius: 1/@r;
        }
    }
    &.left.active {
        &::before {
            animation: transformAnimate 0.2s ease-in-out;
        }
    }
    &.right.active {
        &::before {
            animation: transformAnimateRight 0.2s ease-in-out;
        }
    }
}

@keyframes transformAnimate {
    0% {
        transform:translateX(-100%);
    }
    100% {
        transform:translateX(0);
    }  
}

@keyframes transformAnimateRight {
    0% {
        transform:translateX(100%);
    }
    100% {
        transform:translateX(0);
    }  
}
  1. 遇到的问题

scene : tab栏切换,对应的内容页的动画用, transition-group实现, 使用的时报错 <transition-group> children must be keyed:

key : 这里使用是 vue 中自带的组建 transition-group ,其具体实现原理可以参考官方文档,这里不再赘述,关键字是key , 这里的key是识别当前选中的唯一标识。 name 中的 transitionDir 代表 style 中 class 类的前缀 slide-right || slide-left

<transition-group 
    tag="div"  
    appear
    :name="transitionDir" >
    <div v-show="selectTabFlag==='businessInformation' " :key=" `businessInformation`">
        <BusinessInformation :businessData ="businessInfo" ></BusinessInformation>
    </div>
</transition-group>

// style 
.slide-right-enter-active,
.slide-right-leave-active,
.slide-left-enter-active,
.slide-left-leave-active {
    will-change: transform;
    transition: all .3s;
    position: absolute;
    width:100%;
    left:0;
}
.slide-right-enter {
    transform: translateX(-100%);
}
.slide-right-leave-active {
    transform: translateX(100%);
}
.slide-left-enter {
    transform: translateX(100%);
}
.slide-left-leave-active {
    transform: translateX(-100%);
}

总结 : 类似抛转引玉,transition transition-group 也可以结合 animate.css 其用法及原理还是比较的有意思,有时间会总结一版 ;在实现tab切换的时候,搜索,发现同类总结并不多,大多一些小demo,这里列举希望提供一种实现的思路方法发散大家的思维,时间紧凑并没有延伸,如有错误还望指正。

 类似资料: