## [使用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
- 玫瑰圖