原生js实现轮播图与抖音短视频滑动

        这是我防抖音项目的第一篇文章,我也会在后面做项目的过程中不断总结知识点,添加到这个专栏。感谢张大大的项目来源:https://github.com/zyronon/douyin


  基础结构(template与style)

        我们先设置father为视窗,内嵌box作为放置轮播图的容器,box的长度为轮播图数量乘以father宽度,接着在father设置css属性overflow:hidden;截掉box里除了第一个轮播图多出的部分,再将装载轮播图片的son类div放入box

视窗father:设置超出隐藏属性overflow:hidden

轮播图容器box:根据轮播数量设宽度为n*100%

为什么需要一个box:如果没有box直接father嵌套son,轮播图会自动分割father宽度无视css属性

<template>
<div @pointerdown="onPointerdown" class="father">
    <div class="box" :style="{ transform: 'translateX(' + translateX + '%)' }">
        <div class="son" style="background-color: yellow;">请左右滑动1</div>
        <div class="son" style="background-color: blue;" >请左右滑动2</div>
        <div class="son" style="background-color: gray;" >请左右滑动3</div>
        <div class="son" style="background-color: #5b2c6f ;" >请左右滑动4</div>
        <div class="son">
            <div class="short-video-container" :style="{ transform: 'translateY(' + videoTranslateY + '%)' }">
                <div class="video">请上下滑动1</div>
                <div class="video">请上下滑动2</div>
                <div class="video">请上下滑动3</div>
                <div class="video">请上下滑动4</div>
                <div class="video">请上下滑动5</div>
                <div class="video">请上下滑动6</div>
                <div class="video">请上下滑动7</div>
            </div>
        </div>
        <div class="son" style="background-color: red ;">请左右滑动5</div>
    </div>
</div>
</template>

<style lang="scss" scoped>
.father{
    font-size: 20px;
    width: 600px;
    height: 60vh;
    margin: 0 auto;
    overflow: hidden;
    display: flex;
    .box{
        width: 3600px;
        background-color: green;
        display: flex;
        transition: transform 0.3s ease;
        height: 100%;
        .son{
            line-height: 50vh;
            text-align: center;
            user-select: none;
            width: 600px;
            height: 100%;
            overflow: hidden;
            .short-video-container{
                height: 400%;
                transition: transform 0.3s ease;
                width: 600px;
                .video{
                    height: 25%;
                }
            }
        }
    }
}
</style>

     拖动实现(js部分)

        我通过指针事件pointermove,pointerup,pointerdown监听指针(鼠标)在轮播图上的事件,根据每次pointermove移动的距离去加或减box的translateX值,改变box的x轴位置,实现短视频切换的效果

pointerdown:绑定在father上,对全局添加鼠标抬起与移动事件,并记录按下的时间time,坐标startX与startY

pointermove:计算本次移动触发的坐标与上次触发的坐标差,投影到box的translateX上,同时如果是第一次移动,额外判断是竖向还是横向移动

pointerup:三个事件中最复杂的逻辑。需要计算触发时与抬起事件time的时间差,还有抬起坐标与按下坐标的差,判断能否满足跳转视频的临界条件。我将临界条件设置为滑动父容器十分之n的距离或者滑动时间小于400毫秒,如果超过了临界条件就切换到下一个视频,未能到达临界条件则回到当前

<script setup>
import  { ref, onMounted } from 'vue'
const currentPage = ref(5) // 当前页数
const currentVideo = ref(0) // 当前视频
const videoHeight = ref(0)
const translateX = ref(-200/3) // box的唯一
const isDown = ref(false) // 是否按下
const currentX = ref(0) // 迭代的X
const currentY = ref(0) // 迭代的Y
const startX = ref(0) // 起始的X
const startY = ref(0) // 起始的Y
const prevTX = ref(0) 
const prevTY = ref(0)
const time = ref(0) // 起始的时间
const witchWay =ref(false) // 界面切换方向
const activeTopTab = ref(4)  // 默认选中第五页
const videoTranslateY = ref(0) // 短视频div的位移
const onPointerdown = (e) => {
    // 记录一开始的    
    time.value = performance.now() //获取高精度时间
    prevTX.value = translateX.value
    prevTY.value = videoTranslateY.value
    startX.value = currentX.value = e.clientX
    startY.value = currentY.value = e.clientY
    
    isDown.value = true
    // 暂时去掉三个动画效果
    document.querySelector('.box').style.transition = 'none'
    document.querySelector('.short-video-container').style.transition = 'none'
    // 往视窗增加抬起和移动事件监听
    document.addEventListener('pointerup', onPointerup)
    document.addEventListener('pointermove', onPointermove)
}

const onPointermove = (e) => {
    if (isDown.value === true){
        if ( !witchWay.value ){
            // 判断滑动方向
            if (Math.abs(e.clientX - currentX.value) > Math.abs(e.clientY - currentY.value))
                witchWay.value = 'HORIZONTAL'
            else if(Math.abs(e.clientX - currentX.value) < Math.abs(e.clientY - currentY.value) && currentPage.value === 5){
                witchWay.value = 'VERTICAL'
            }
        }else{
            if (witchWay.value === 'HORIZONTAL'){
                // 边界处理
                const disBetMove = e.clientX - currentX.value
                if ((disBetMove > 0 && currentPage.value === 1) || 
                (disBetMove < 0 && currentPage.value === 6) ) return

                translateX.value += disBetMove / 24
            }
            else videoTranslateY.value += 100 * (e.clientY - currentY.value) / videoHeight.value
            currentX.value = e.clientX
            currentY.value = e.clientY
        }
    }
}
const onPointerup = () => {
    const horizontalGap = translateX.value - prevTX.value
    const verticalGap = videoTranslateY.value - prevTY.value
    isDown.value = false
    const nowTime = performance.now()
    // 解除动画限制
    document.querySelector('.box').style.transition = 'transform 0.3s ease'
    document.querySelector('.short-video-container').style.transition = 'transform 0.3s ease'
    // 根据方向决定去留
    if (witchWay.value === 'HORIZONTAL'){
        // 向左or向右
        if ( (nowTime - time.value < 400 && horizontalGap )  || Math.abs( horizontalGap ) > 100/18){
            currentPage.value += (horizontalGap > 0 ? -1 : 1)
            activeTopTab.value += (horizontalGap > 0 ? -1 : 1)
            translateX.value = prevTX.value +( horizontalGap > 0 ? 100/6 : -100/6 );
            // 判断下划线的临界条件 进行临界移动
        }
        // 归位
        else translateX.value = prevTX.value
    }else if(witchWay.value === 'VERTICAL'){
        if ( (nowTime - time.value < 400 && verticalGap )  || Math.abs( verticalGap ) > 100 / 12 ){
            videoTranslateY.value = prevTY.value + ( verticalGap > 0 ? 25 : -25 );
            currentVideo.value += (verticalGap > 0 ? -1 : 1)
        }
        else videoTranslateY.value = prevTY.value
    }
    witchWay.value = ''

    // 移除两个增加的事件
    document.removeEventListener('pointerup', onPointerup)
    document.removeEventListener('pointermove', onPointermove)
}
onMounted( () => {
    videoHeight.value = document.querySelector('.short-video-container').clientHeight
})
</script>

为什么不用touch或mouse事件:指针事件可双端兼容

为什么不直接在father绑定所有指针事件:防止鼠标滑动到容器之外失去判断

为什么需要在pointermove移动时解除动画:不解除的话,拖动会有延迟,但是最后的视频切换有需要动画,所以在pointerup中又加回来

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值