首页 > 解决方案 > Vue.js - 提高无限列表循环的性能

问题描述

我需要一个用户列表来无限地垂直循环。我知道我应该使用“translateY”而不是“top”来处理那种东西——但我不知道怎么做。我已经完成了“顶级”版本并且可以正常工作。有什么想法可以改善吗?

多谢你们!

Codepen 中的示例

<div id="app">
    <div id="rows">
        <div class="row" v-for="row in rows" v-bind:style="{ top: row.top + 'px' }">
            {{row.id}}
        </div>
    </div>
</div>

<script>
    const app = new Vue({
        el: '#app',
        data() {
            return {
                rows: []
            }
        },
        created() {
            for (let i = 0; i < 30; i++) {
                this.rows.push({
                    id: i,
                    top: i * 40
                })
            }
            setInterval(() => {
                window.requestAnimationFrame(this.update);
            }, 16);
        },
        methods: {
            update() {
                this.rows.forEach(row => {
                    row.top -= 0.5
                });
                if (this.rows[0].top <= -40) {
                    this.rows.push({
                        id: this.rows[0].id,
                        top: (this.rows.length - 1) * 40
                    })
                    this.rows.shift();
                }
            }
        }
    })
</script>
<style>
    #rows {
        position: relative;
    }
    .row {
        position: absolute;
        width: 100%;
        height: 40px;
        border: 1px solid black;
    }
</style>

标签: javascriptcssvue.jsanimation

解决方案


这是我的尝试:

new Vue({
  el: '#app',

  data() {
    const rows = []

    for (let i = 0; i < 30; i++) {
      rows.push({
        id: i
      })
    }

    return {
      offset: 0,
      rows
    }
  },

  mounted () {
    this.frameTime = Date.now()
    
    const animate = () => {
      this.animationId = requestAnimationFrame(() => {
        this.update()
        animate()
      })
    }
    
    animate()
  },
  
  beforeDestroy () {
    cancelAnimationFrame(this.animationId)
  },

  methods: {
    update() {
      const now = Date.now()
      const elapsed = now - this.frameTime
      
      this.offset -= elapsed / 16
      this.frameTime = now

      if (this.offset < -400) {
        while (this.offset < -40) {
          this.rows.push(this.rows.shift())
          this.offset += 40
        }      
      }
    }
  }
})
* {
  box-sizing: border-box;
}

#rows {
  border: 1px solid #f00;
  height: 200px;
  overflow: hidden;
}

.row {
  width: 100%;
  height: 40px;
  border: 2px solid black;
}
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<div id="app">
  <div id="rows">
    <div :style="{ transform: `translateY(${Math.round(offset)}px)` }">
      <div
        v-for="row in rows"
        :key="row.id"
        class="row"
      >
        {{row.id}}
      </div>
    </div>
  </div>
</div>

您不一定要进行我所做的所有更改,如果您愿意,可以有选择地进行大多数更改。主要变化是:

  1. 将 akey放在列表项上,以便 Vue 在 shuffle 发生时移动 DOM 节点而不是更新所有节点。
  2. 使用包装器<div>,这样实际上只有一个元素在移动(我摆脱了绝对定位作为其中的一部分)。
  3. 根据translateY要求使用 。
  4. 摆脱setInterval,你应该只需要requestAnimationFrame这个。通过跟踪已经过去的时间来检查动画速度。
  5. 当一行跳转时,我只是将对象移动到数组的末尾而不是制作副本。
  6. 当组件被销毁时动画被取消。

更新:

三个进一步的变化:

  1. 我已添加box-sizing: border-box以修复计算中的 2px 不准确性。
  2. DOM 节点重新排序现在批处理,每 400 像素发生一次。不知道这是否真的是一个好主意,对于这样一个简单的例子来说,它并没有什么区别。
  3. 我已经四舍五入translateY以使用整个像素。对我来说,这看起来稍微好一些,但在像素比更高的屏幕上,我可以想象它可能看起来更糟。

根据具体情况,还有可能适用的进一步优化。

  1. 可以省略不可见的行。
  2. 通过将 应用于每一行可以完全避免重新排序translateY,尽管对于大量行可能不切实际。

由于需要行跳回底部,因此使用过渡或 CSS 动画对此进行动画处理会很棘手。如果每一行都是独立动画的,我不确定保持所有动画同步有多容易。


推荐阅读