使用js實現列表無限循環滾動


  最近的業務有涉及到需要將列表做成無限循環滾動,即第一個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()方法一致。

  至此,將列表無限循環滾動就實現出來了,完整代碼在下面,有需自取。

<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>

  demo地址:https://github.com/16hmchen/infiniteScroll

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM