无限滚动列表:分为单步滚动和循环滚动两种方式

<template>
<div class="box" :style="{width:widthX,height:heightY}"
@mouseenter="mEnter" @mouseleave="mLeave"
>
<div
class="indefiniteScroll"
:style="{width:widthX,height:heightY,transform:`translateY(${top+'px'})`}"
>
<slot></slot>
</div>
<div
v-if="isFull"
class="indefiniteScroll"
: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>

Vue组件封装之无限滚动列表的更多相关文章

  1. Android 高级UI设计笔记09:Android如何实现无限滚动列表

    ListView和GridView已经成为原生的Android应用实现中两个最流行的设计模式.目前,这些模式被大量的开发者使用,主要是因为他们是简单而直接的实现,同时他们提供了一个良好,整洁的用户体验 ...

  2. Android 高级UI设计笔记09:Android实现无限滚动列表

    1. 无限滚动列表应用场景: ListView和GridView已经成为原生的Android应用实现中两个最流行的设计模式.目前,这些模式被大量的开发者使用,主要是因为他们是简单而直接的实现,同时他们 ...

  3. 【js】我们需要无限滚动列表吗?

    无限滚动列表,顾名思义,是能够无限滚动的列表(愿意是指那些能够不断缓冲加载新数据的列表的).但是,我们真的需要这样一个列表吗?在PC端,浏览器的性能其实已经能够满足海量dom节点的渲染刷新(笔者经过简 ...

  4. vue组件封装及父子组件传值,事件处理

    vue开发中,把有统一功能的部分提取出来,作为一个独立的组件,在需要使用的时候引入,可以有效减少代码冗余.难点在于如果封装,使用,如何传参,派发事件等,我会采取倒叙的方式进行说明.(本文总结于Vue2 ...

  5. 附件上传vue组件封装(一)

    //父页面部分 <attachment @newFileList="newFileList" :operationType="operationType" ...

  6. vue组件封装选项卡

    <template> <myMenu :arr='arr' :arrcontent='content'></myMenu> </template> &l ...

  7. Vue 组件封装发布到npm 报错 Uncaught TypeError: Cannot read property 'toLowerCase' of undefined

    Uncaught TypeError: Cannot read property 'toLowerCase' of undefined 原因是 没有导出 export default { name:& ...

  8. 移动端无限滚动 TScroll.vue组件

    // 先看使用TScroll.vue的几个demo 1.https://sorrowx.github.io/TScroll/#/ 2. https://sorrowx.github.io/TScrol ...

  9. vue2.0 如何自定义组件(vue组件的封装)

    一.前言 之前的博客聊过 vue2.0和react的技术选型:聊过vue的axios封装和vuex使用.今天简单聊聊 vue 组件的封装. vue 的ui框架现在是很多的,但是鉴于移动设备的复杂性,兼 ...

随机推荐

  1. JavaScript学习07(jQuery)

    jQuery 什么是JQuery jQuery 由 John Resig 于 2006 年创建.它旨在处理浏览器不兼容性并简化 HTML DOM 操作.事件处理.动画和 Ajax. 十多年来,jQue ...

  2. Java之JSP

    JSP JSP简介 JSP指的是 JavaServerPages ,Java服务器端页面,也和Servlet一样,用来开发动态web JSP页面中可以嵌入java代码为用户提供动态数据 JSP原理 J ...

  3. comm tools

    RTL:寄存器传输级别 LRM:语言参考手册 FSM:有限状态机 EDIF:电子数据交换格式 LSO:库搜索目录 XCF:XST 约束条件 1. par -ol. high  命令总是 '-'开头,参 ...

  4. [TcaplusDB知识库]数据库支撑底盘引擎计算层介绍

    在上次的TcaplusDB知识库中,TcaplusDB君为大家讲解了TcaplusDB所用的基于HASH表的Key-value存储引擎TXHDB.存储引擎作为数据库的支撑底盘,其重要性无可置疑,而在本 ...

  5. 黑马JVM教程——自学笔记(三)

    四.类加载与字节码技术 4.1.类文件结构 首先获得.class字节码文件 方法: 在文本文档里写入java代码(文件名与类名一致),将文件类型改为.java java终端中,执行javac X:.. ...

  6. ReentrantLock可重入锁lock,tryLock的区别

    void lock(); Acquires the lock. Acquires the lock if it is not held by another thread and returns im ...

  7. prism 的学习网站

    C#的学习网址: https://www.cnblogs.com/zh7791

  8. C# ThreadLocal源码追踪

    ThreadLocal 字段成员: private Func<T>? _valueFactory; 一个获取默认值的委托 不同线程共享此成员. [ThreadStatic] private ...

  9. 输入npm install 报错node-sass@4.13.0 postinstall:`node scripts/build.js` Failed at the node-sass@4.13.0

    这个是因为sass安装时获取源的问题,先修改sass安装的源,再运行npm install就成功了 npm config set sass_binary_site=https://npm.taobao ...

  10. Git脑图

    ps:有时我们想一台有不同的git账号对应不同的git仓库时(gitLab/gitHub)时,除了全局的用户配置定义,我们可以为不同仓库自定义不同用户名和邮件 1.查询全局的配置:git config ...