这是我防抖音项目的第一篇文章,我也会在后面做项目的过程中不断总结知识点,添加到这个专栏。感谢张大大的项目来源: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中又加回来
标签:box,const,轮播,father,value,js,抖音,滑动,ref From: https://blog.csdn.net/violeteverdack/article/details/143866678