无限滚动列表:分为单步滚动和循环滚动两种方式
<template> <div :style="{width:widthX,height:heightY}" @mouseenter="mEnter" @mouseleave="mLeave" > <div :style="{width:widthX,height:heightY,transform:`translateY(${top+'px'})`}" > <slot></slot> </div> <div v-if="isFull" :style="{width:widthX,height:heightY,transform:`translateY(${top2+'px'})`}" > <slot></slot> </div> </div> </template> <script lang="ts"> import { defineComponent, ref, watch, onUnmounted, onMounted, reactive, } from "vue"; export default defineComponent({ props:{ width:{ // 盒子宽 type: [Number,String], default: '400' }, height:{ // 盒子高 type: [Number,String], default: '300' }, scrollList: { // 数据列表 type: Array, default: [] }, direction:{ // 滚动方向 top | bottom type: String, defauilt: 'top' }, moveType:{ // 滚动类型,0:默认,1:单步停顿 type: [Number,String], default: 0 }, speed:{ // 速度1-5 type: [Number,String], default: 1 }, pauseTime:{ // 停顿时间 type: [Number,String], default: 300 }, singleHeight:{ // 单行高度 type: [Number,String], default: 30 } }, setup(props,context){ let widthX:any = ref('') let heightY:any = ref('') let top:any = ref('0') let top2:any = ref('0') let timer:any = ref(null) let dis:any = ref(0) let options:any = reactive({ direction: 'top', moveType: 0, // 0默认滚动,1单步停顿 speed: 1, }) let isFull = ref(true) // 数据是否充满盒子 let isIn = false onMounted(()=>{ methods.getXY() methods.setOption() if(Number(props.singleHeight)*props.scrollList.length<=Number(props.height)){ // 如果传入的数据没有占满盒子就不滚动 isFull.value = false return } else { isFull.value = true methods.scroll('','') } }) watch(()=>props.scrollList,()=>{ if(timer) { window.cancelAnimationFrame(timer) if(options.direction == 'top') { top.value = '0' top2.value = Number(props.singleHeight)*props.scrollList.length - Number(props.height) // 初始位置 } else { top.value = -Number(props.singleHeight)*props.scrollList.length top2.value = -Number(props.height) // 初始位置 } } if(Number(props.singleHeight)*props.scrollList.length<=Number(props.height)){ // 如果传入的数据没有占满盒子就不滚动 isFull.value = false return } else { isFull.value = true methods.scroll('','') } },{ deep:true, }) onUnmounted(()=>{ if(timer) { window.cancelAnimationFrame(timer) } }) let methods = { getXY(){ // 盒子宽高 widthX.value = props.width + 'px' heightY.value = props.height + 'px' }, setOption(){ // 参数设置 options.direction = props.direction options.moveType = Number(props.moveType) if(props.speed<1){ // 限制速度 options.speed = 1 } else if(props.speed>5){ options.speed = 5 } else { options.speed = Number(props.speed) } }, scroll(currentTop:string,currentTop2:string){ // 滚动 if(options.direction == "bottom"){ // 初始位置 if(currentTop){ top.value = currentTop // 鼠标移入移出位置 top2.value = currentTop2 // 初始位置 } else { top.value = -Number(props.singleHeight)*props.scrollList.length top2.value = -Number(props.height) // 初始位置 } } else { if(currentTop2){ top2.value = currentTop2 } else { top2.value = Number(props.singleHeight)*props.scrollList.length - Number(props.height) // 初始位置 } } switch(options.moveType){ case 0: if(options.direction == "top") { methods.baseMoveTop() } else if(options.direction == "bottom"){ methods.baseMoveBottom() } break case 1: if(options.direction == "top") { methods.singleMoveTop() } else if(options.direction == "bottom"){ methods.singleMoveBottom() } break } }, mEnter(){ // 鼠标移入 if(isFull.value) isIn = true }, mLeave(){ // 鼠标移出 if(isFull.value){ isIn = false methods.scroll(top.value,top2.value) } }, baseMoveTop(){ // 默认-向上滑动循环 top.value = -options.speed + Number(top.value)// 移动计算 top2.value = -options.speed + Number(top2.value)// 移动计算 if(Number(top.value)<=-Number(props.singleHeight)*props.scrollList.length){ top.value = 0 top2.value = Number(props.singleHeight)*props.scrollList.length - Number(props.height) // 初始位置 } if(!isIn) timer = window.requestAnimationFrame(methods.baseMoveTop) }, baseMoveBottom(){ // 默认-向下滑动循环 top.value = options.speed + Number(top.value) // 移动计算 top2.value = options.speed + Number(top2.value) // 移动计算 if(Number(top.value)>=0){ top.value = -Number(props.singleHeight)*props.scrollList.length top2.value = -Number(props.height) // 初始位置 } if(!isIn) timer = window.requestAnimationFrame(methods.baseMoveBottom) }, singleMoveTop(){ // 单步-向上滑动循环 // let dir = 1 dis.value = options.speed + dis.value top.value = -options.speed + Number(top.value) // 移动计算 top2.value = -options.speed + Number(top2.value)// 移动计算 if(Number(top.value)<=-Number(props.singleHeight)*props.scrollList.length){ top.value = 0 top2.value = Number(props.singleHeight)*props.scrollList.length - Number(props.height) // 初始位置 } if(dis.value >= Number(props.singleHeight)){ dis.value = 0 window.cancelAnimationFrame(timer) let nowTime = 0 let lastTime = Date.now() function pause() { // 停顿时间计算 nowTime = Date.now() if(nowTime -lastTime >= Number(props.pauseTime)){ lastTime = nowTime window.requestAnimationFrame(methods.singleMoveTop) window.cancelAnimationFrame(timer) return } timer = window.requestAnimationFrame(pause) } pause() return } if(!isIn) timer = window.requestAnimationFrame(methods.singleMoveTop) }, singleMoveBottom(){ // 单步-向下滑动循环 dis.value = Number(options.speed) + dis.value top.value = options.speed + Number(top.value) // 移动计算 top2.value = options.speed + Number(top2.value) // 移动计算 if(Number(top.value)>=0){ top.value = -Number(props.singleHeight)*props.scrollList.length top2.value = -Number(props.height) // 初始位置 } if(dis.value >= Number(props.singleHeight)){ // 滚动一行后停止动画,停顿时间之后继续动画 dis.value = 0 window.cancelAnimationFrame(timer) let nowTime = 0 let lastTime = Date.now() function pause() { // 停顿时间计算 nowTime = Date.now() if(nowTime -lastTime >= Number(props.pauseTime)){ lastTime = nowTime window.requestAnimationFrame(methods.singleMoveBottom) window.cancelAnimationFrame(timer) return } timer = window.requestAnimationFrame(pause) } pause() return } if(!isIn) timer = window.requestAnimationFrame(methods.singleMoveBottom) } } return{ widthX, heightY, timer, options, dis, isFull, top, top2, ...methods, } } }); </script> <style lang="postcss" scoped> .indefiniteScroll{ margin: 0; padding: 0; user-select: none; padding: 1px; /* transition: all 0.5s; */ .scroll-item{ height: 30px; line-height: 30px; font-size: 14px; color: rgb(0, 0, 0); p{ margin: 0; padding: 0; } } .scroll-item:nth-of-type(1){ margin-top: 0; } } .box{ overflow: hidden; } </style>