实现效果
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>