## [使用js实现列表无限循环滚动](https://www.cnblogs.com/hmchen/p/13934623.html)
最近的业务有涉及到需要将列表做成无限循环滚动,即第一个element滚出边界之后需要自动跳到队尾,参与下一轮滚动,达到无限滚动的效果。
下面讲一下思路。
~~~
// js
<div class="scroll-container">
<div
v-for="index in 8"
:key="index"
class="scroll-item"
:style="styleFormatter(index - 1)"
ref="scrollItem"
>
{{ index }}
</div>
</div>
// css
.scroll-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: row;
position: relative;
overflow: hidden;
}
.scroll-item {
display: flex;
flex-shrink: 0;
width: 200px;
height: 100%;
position: absolute;
transition-property: left;
transition-timing-function: linear;
}
~~~
初始化时,会将scroll-item的定位改为绝对定位,相对元素是scroll-container,然后根据每个元素的宽度给每个scroll-item赋上left值,这样就可以让每个元素都无缝衔接上了(比如说每个元素的宽度都是200px,那么第一个元素left就是0,第二个元素left就是200px,以此类推,当然也可以设置为不等宽以及加上margin,思路是一样的)。scroll-item包含在scroll-container容器内,首先scroll-container需要将overflow设置成hidden,将超出范围的元素进行隐藏。至于滚动效果,本demo使用的是transition来控制元素滚动动画。在已知每个元素的宽度(假设为width)的情况下,可以将left从初始位置滚动到-width,则元素会完全隐藏掉,再通过修改css中transition-duration属性来控制动画执行时间即可。
以本demo为例,每个scroll-item的宽度都为200px,滚动速度是30px/s,那么第一个元素的transition-duration就是(200 / 30) = 6.67s了,以此类推。具体实现如下:
~~~
// 根据索引计算当前元素所在位置
styleFormatter (index) {
return {
backgroundColor: this.colorList[index],
left: `${index * this.width}px`
}
}
// 控制滚动开始
scroll () {
const elementList = this.$refs.scrollItem
elementList.forEach(ele => {
ele.style.transitionDuration = `${(ele.offsetLeft + this.width) / this.speed}s`
ele.style.left = `-${this.width}px`
})
}
~~~
styleFormatter()是根据每个元素的索引以及宽度来初始化每个元素的left,在本demo中还加上了background-color方便识别。重点是scroll()里面,这里面用到了我们小学二年级学到的公式,路程 = 速度 x 时间,已知该元素需要行走的路程是自身的位置offsetLeft加上自身的宽度width,以及速度speed(自定义),易得该元素需要行走的时间time,也就是对应这里面的动画执行时间transition-duration。这样就可以保证每个scroll-item的运行速度是一致的,以及每一个元素是能够衔接起来的。
这样就能够实现一轮动画了,下面开始思考如何实现无限滚动。当第一个元素完成动画(已经完全隐藏了)的时候,将其插入到剩下元素中的队尾,重新开始执行动画,是不是就可以实现无限滚动了。我们知道元素是有个事件叫transitionend,可以监听当前的动画是否已经执行完毕。那么事情就变得简单了,试试看:
~~~
// 初始化listener,监听滚动动画
// 当元素完全滚动至显示范围外时,则将该元素插入队尾
initListener () {
const elementList = this.$refs.scrollItem
elementList.forEach(ele => {
// 当动画结束后会触发transitionend事件
ele.addEventListener('transitionend', () => {
// 计算队尾位置
const lastestElementPosition = this.lastestElementPosition()
ele.style.transitionDuration = `0s`
ele.style.left = `${lastestElementPosition + this.width}px`
// 渲染完毕之后开始滚动
this.$nextTick(() => {
ele.style.transitionDuration = `${(ele.offsetLeft + 200) / this.speed}s`
ele.style.left = `-${this.width}px`
})
})
})
}
// 计算最后一个元素的位置
lastestElementPosition () {
const elementList = this.$refs.scrollItem
return Math.max(...elementList.map(ele => {
// 注意,这里不能直接读取ele.style.left,这样只能读到终点位置的偏移量,而我们需要的是当前元素的实际位置,需要通过getComputedStyle来获取
const styles = getComputedStyle(ele, null)
return parseFloat(styles.left)
}))
}
~~~
- 通过addEventListener来监听transitionend事件,当动画执行完毕之后,把当前元素插入到队尾。这时候需要去找到执行列表的最后一个元素,通过每个元素的left值来判断,left值最大则代表该元素是滚动列表中的最后一个元素。注意,这里需要先将transition-duration设置为0,不然元素从当前位置移动至队尾会有一个动画过程。
- 将元素移动至队尾之后,剩下的时候就好处理了,处理方式跟scroll()方法一致。
- 至此,将列表无限循环滚动就实现出来了,完整代码在下面,有需自取。
```vue
<template>
<div class="main">
<div class="scroll-container">
<div v-for="index in 8" :key="index" class="scroll-item" :style="styleFormatter(index - 1)" ref="scrollItem">
{{ index }}
</div>
</div>
<div class="controller">
<button class="btn" @click="toggleClick">{{ isScrolling ? '暂停滚动' : '开始滚动' }}</button>
</div>
</div>
</template>
<script>
export default {
data () {
return {
colorList: [
'gray',
'antiquewhite',
'aquamarine',
'cadetblue',
'darkgoldenrod',
'mediumaquamarine',
'violet',
'royalblue'
],
// 每个元素的宽度
width: 200,
// 滚动速度
speed: 100,
isScrolling: false
}
},
mounted () {
this.initListener()
},
methods: {
toggleClick () {
if (this.isScrolling) {
this.isScrolling = false
this.pause()
} else {
this.isScrolling = true
this.scroll()
}
},
// 根据索引计算当前元素所在位置
styleFormatter (index) {
return {
backgroundColor: this.colorList[index],
left: `${index * this.width}px`
}
},
// 控制滚动开始
scroll () {
const elementList = this.$refs.scrollItem
elementList.forEach(ele => {
ele.style.transitionDuration = `${(ele.offsetLeft + this.width) / this.speed}s`
ele.style.left = `-${this.width}px`
})
},
// 暂停滚动
pause () {
const elementList = this.$refs.scrollItem
elementList.forEach(ele => {
const styles = getComputedStyle(ele, null)
console.log(styles.left)
ele.style.transitionDuration = '0s'
ele.style.left = `${styles.left}`
})
},
// 初始化listener,监听滚动动画
// 当元素完全滚动至显示范围外时,则将该元素插入队尾
initListener () {
const elementList = this.$refs.scrollItem
elementList.forEach(ele => {
// 当动画结束后会触发transitionend事件
ele.addEventListener('transitionend', () => {
// 计算队尾位置
const lastestElementPosition = this.lastestElementPosition()
ele.style.transitionDuration = `0s`
ele.style.left = `${lastestElementPosition + this.width}px`
// 渲染完毕之后开始滚动
this.$nextTick(() => {
ele.style.transitionDuration = `${(ele.offsetLeft + 200) / this.speed}s`
ele.style.left = `-${this.width}px`
})
})
})
},
// 计算最后一个元素的位置
lastestElementPosition () {
const elementList = this.$refs.scrollItem
return Math.max(...elementList.map(ele => {
// 注意,这里不能直接读取ele.style.left,这样只能读到终点位置的偏移量,而我们需要的是当前元素的实际位置,需要通过getComputedStyle来获取
const styles = getComputedStyle(ele, null)
return parseFloat(styles.left)
}))
}
}
}
</script>
<style scoped>
.scroll-container {
width: 100%;height: calc(100% - 50px);display: flex;
flex-direction: row;position: relative;overflow: hidden;
}
.scroll-item {
display: flex;flex-shrink: 0;width: 200px;height: 100%;
position: absolute;transition-property: left;
transition-timing-function: linear;
}
.btn {
margin-top: 20px;height: 30px;line-height: 30px;
}
</style>
```
- 前端指南
- 基础
- HTML、HTTP、web综合问题
- css部分
- 学习指南
- 开发指南
- css指南
- JavaScript
- 视图、文件
- canvas
- 二维码的生成
- 64码及图片
- weui
- Promise
- 第三方js
- 网络请求
- 字符串,数组,时间
- 时间类
- Css
- 布局封装
- 媒体布局
- 九宫格图片自适应
- 两行显示,且省略
- uni-app
- uniapp踩坑指南
- 表单类
- 商品规格
- 页面操作
- H5端返回按钮不显示
- H5解决浏览器跨域问题
- uView——Waterfall 瀑布流
- uniapp中使用复制功能(复制文本到粘贴板)
- 动态导航栏的实现
- React
- React基础
- 微信小程序
- 上传多图
- uni-app 微信小程序生成小程序码二维码带参数
- 小程序分享图片给好友,到朋友圈,保存到本地
- 缓存封装
- Vue
- 深度作用选择器deep
- 使用js实现列表无限循环滚动(横向)
- js 无限循环垂直滚动列表
- 可视化
- AntV
- 玫瑰图