vue3-实现无缝滚动效果

实现效果

vue3项目中使用到无缝滚动效果,于是封装了组件实现,效果如下:
在这里插入图片描述

实现思路

和实现无缝轮播图的思路基本一样,首页整体滚动效果可以利用css3的位移实现(transform/translate)即可,自动滚动使用到了requestAnimationFrame方法。
整体思路如下:

1、声明变量保存容器移动的偏移量,封装onScroll函数实现容器的移动
2、复制一份首次展示出来的那些数据 并 添加到数组末尾 --- 为实现滚动位置重置做铺垫
3、在onMounted钩子函数中调用函数实现自动滚动
4、在鼠标移入的时候暂停
5、在鼠标移出的时候重新开始滚动

结构

<div ref="listBoxRef" class="list-box auto-scrll-list" @mouseenter="onMouseOver" @mouseleave="onMouseOut">
    <ul ref="listRef" class="list">
        <li :style="{height: liHeight,lineHeight: liHeight }" 
        v-for="(item,index) in list" :key="index">
        <slot :row="item" :index="index">{{ item }}</slot>
    </li>
    </ul>
</div>

实现移动onScroll

const count = ref(0) // 移动的位置
const onScroll = ()=>{
    count.value++
    if(count.value>=parseInt(props.liHeight)*(props.list.length-copyList.value.length)){
        // 当移动的位置大于等于当前列表的高度的时候 重置移动位置
        count.value = 0
    }
    if(listRef.value){
        listRef.value.style.transform = `translateY(-${count.value}px)`
        requestId.value = requestAnimationFrame(onScroll)
    }
}

复制一份数据到数组底部

// 开始滚动
const start = ()=>{
    if (requestId.value) {
        // 有动画 先移除动画
        window.cancelAnimationFrame(requestId);
    }else{
        // 计算当前容器高度 能够展示多少条数据
        let listNum = listBoxRef.value.offsetHeight / parseInt(props.liHeight)
        // 复制一份首次展示出来的那些数据 并 添加到数组末尾 --- 为实现滚动位置重置做铺垫
        const newCopyList = ref([])
        copyList.value = props.list.slice(0, listNum)
        if(copyList.value.length<listNum){
            // 当渲染的数据较少的时候 添加空字符串补充
            let leng = listNum- copyList.value.length
            for(let i=0;i<leng;i++){
                newCopyList.value.unshift('')
            }
        }
        props.list.push(...[...newCopyList.value,...copyList.value])
        // 操作数据后更新视图 强制更新
        const internalInstance = getCurrentInstance()
        internalInstance.ctx.$forceUpdate();
    }
    // 开始动画
    requestId.value = requestAnimationFrame(onScroll)
}

在onMounted钩子函数中调用函数实现自动滚动

onMounted(()=>{
    // 进入页面开始滚动
    if(props.list.length > 0) {
        start()
    }
})

鼠标移入移除实现滚动和暂停

// 鼠标移入
const onMouseOver = ()=>{
    stop()
}
// 鼠标移出
const onMouseOut = ()=>{
    if(props.list.length > 0) {
        start()
    }
}

完整代码

<script setup>
import { onMounted, ref } from "vue"

const props = defineProps({
    liHeight: {
        type: String,
        default: "30px"
    },
    list: {
        type: Array,
        default: ()=>{
            const list = []
            for(let i = 0; i < 30; i++){
                list.push(`内容${i}`)
            }
            return list
        }
    }
})

// 初始化数据
const copyList = ref([]) // 复制的数据
const requestId = ref(null) // 保存帧动画id
const listBoxRef = ref(null) // 渲染数据的大容器 -- 限制高度
const listRef = ref(null) // 渲染数据的容器
const count = ref(0) // 移动的位置

const onScroll = ()=>{
    count.value++
    if(count.value>=parseInt(props.liHeight)*(props.list.length-copyList.value.length)){
        // 当移动的位置大于等于当前列表的高度的时候 重置移动位置
        count.value = 0
    }
    if(listRef.value){
        listRef.value.style.transform = `translateY(-${count.value}px)`
        requestId.value = requestAnimationFrame(onScroll)
    }
}
import { getCurrentInstance } from 'vue';
// 开始滚动
const start = ()=>{
    if (requestId.value) {
        // 有动画 先移除动画
        window.cancelAnimationFrame(requestId);
    }else{
        // 计算当前容器高度 能够展示多少条数据
        let listNum = listBoxRef.value.offsetHeight / parseInt(props.liHeight)
        // 复制一份首次展示出来的那些数据 并 添加到数组末尾 --- 为实现滚动位置重置做铺垫
        const newCopyList = ref([])
        copyList.value = props.list.slice(0, listNum)
        if(copyList.value.length<listNum){
            // 当渲染的数据较少的时候 添加空字符串补充
            let leng = listNum- copyList.value.length
            for(let i=0;i<leng;i++){
                newCopyList.value.unshift('')
            }
        }
        props.list.push(...[...newCopyList.value,...copyList.value])
        // 操作数据后更新视图 强制更新
        const internalInstance = getCurrentInstance()
        internalInstance.ctx.$forceUpdate();
    }
    // 开始动画
    requestId.value = requestAnimationFrame(onScroll)
}
// 结束滚动
const stop = ()=>{
    if (requestId.value) {
        window.cancelAnimationFrame(requestId.value);
    }
}
onMounted(()=>{
    // 进入页面开始滚动
    if(props.list.length > 0) {
        start()
    }
})
// 鼠标移入
const onMouseOver = ()=>{
    stop()
}
// 鼠标移出
const onMouseOut = ()=>{
    if(props.list.length > 0) {
        start()
    }
}
</script>
<template>
<div ref="listBoxRef" class="list-box auto-scrll-list" @mouseenter="onMouseOver" @mouseleave="onMouseOut">
    <ul ref="listRef" class="list">
        <li :style="{height: liHeight,lineHeight: liHeight }" 
        v-for="(item,index) in list" :key="index">
        <slot :row="item" :index="index">{{ item }}</slot>
    </li>
    </ul>
</div>
</template>
<style lang="scss" scoped>
.list-box{
    height: 300px;
    overflow: hidden;
    border: 1px solid black;
    border-radius: 2px;
    margin: auto;
    padding: 10px;
    // overflow-y: auto;
    .list {
        li{
            height: 30px;
            line-height: 30px;
        }
    }
}
</style>

使用

       import AutoScrollList from './AutoScrollList.vue'
        <AutoScrollList :list="list" style="300px" liHeight="25px" >
            <template v-slot="{row}">
                {{row}}
            </template>
        </AutoScrollList>
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值