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

<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. CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记

    CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记 cas服务器的搭建 导出证书(1和2步骤是找了课程,随便写了一下存记录,不过对于自己测试不投入使用应该不影响) C:\Users\D ...

  2. 【vulapps】Sturcts2 S2-037RCE漏洞复现

    一.漏洞基本信息 S2-037官方公告 CVE编号:CVE-2016-4438漏洞名称:Struts(S2-037)远程代码执行漏洞发布日期:2016.615受影响的软件及系统:Apache stru ...

  3. Proxifier/ProxyChains+reGeorg组合进行内网代理

    在内网渗透过程中,我们经常使用sockes代理工具,本文主要介绍攻击机为windows和linux情况下得使用方式. Win:proxifier+reGeorg 组合 Linux:proxychain ...

  4. SQL 练习28

    查询平均成绩大于等于 85 的所有学生的学号.姓名和平均成绩 SELECT Student.SId,Student.Sname,平均成绩 FROM Student , (SELECT sid,AVG( ...

  5. spring cloud 网管篇zuul

    1, consul 2, zuul 程序的yml 文件 server: port: 8083spring: application: name: zuulInfo # 应用名称 cloud: cons ...

  6. jsoup的Node类

    一.简介 Node类直接继承Object,实现了Cloneable接口,它是一个抽象类,类声明:public abstract class Node extends Object implements ...

  7. 信号量-Semaphore、SemaphoreSlim

    核心类:Semaphore,通过int数值来控制线程个数. * 通过观察构造函数 public Semaphore(int initialCount, int maximumCount);: * in ...

  8. jquery validate 验证插件 解决多个相同的Name 只验证第一个的方案

    方案一:如果 项目里不是只是个别页面 有多个name 验证, 那么利用 prototype 来写,把这段代码加在你所要使用多个name的页面  的js初始化里 即可 if ($.validator) ...

  9. Go版本管理--go.sum

    目录 1. 简介 2. go.sum文件记录 3. 生成 4.校验 5.校验和数据库 1. 简介 为了确保一致性构建,Go引入了go.mod文件来标记每个依赖包的版本,在构建过程中go命令会下载go. ...

  10. CSS截取字段,让过长的字段结尾变成省略号(IE有效)

    text-overflow:ellipsis;overflow:hidden;<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transiti ...