vue中實現一個虛擬列表


應用場景

  前端的業務開發中會遇到不使用分頁方式來加載長列表的需求。如在數據長度大於 1000 條情況,DOM 元素的創建和渲染需要的時間成本很高,完整渲染列表所需要的時間不可接受,同時會存在滾動時卡頓問題;

  解決該卡頓問題的重點在於如何降低長列表DOM渲染成本問題,文章將介紹通過虛擬列表渲染的方式解決該問題。

實現思路

  虛擬列表的核心思想為可視區域渲染,在頁面滾動時對數據進行截取、復用DOM進行展示的渲染方式。

  實現虛擬列表就是處理滾動條滾動后的可見區域的變更,其中具體步驟如下:

  1. 計算當前可見區域起始數據的 startIndex
  2. 計算當前可見區域結束數據的 endIndex
  3. 計算當前可見區域的數據,並渲染到頁面中
  4. 計算 startIndex 對應的數據在整個列表中的偏移位置 startOffset,並設置到列表上

 基礎實現

  我們首先要考慮的是虛擬列表的 HTML、CSS 如何實現:

  • 列表元素(.list-view)使用相對定位
  • 使用一個不可見元素(.list-view-phantom)撐起這個列表,讓列表的滾動條出現
  • 列表的可見元素(.list-view-content)使用絕對定位,left、right、top 設置為 0
<template>
     <div 
    class="list-view"
    :style="{
        height: `${height}px`
    }" 
    @scroll="handleScroll">
        <div
        class="list-view-phantom"       
        :style="{
            height: contentHeight
        }">
        </div>
        <ul
        ref="content"
        class="list-view-content">
            <li
                class="list-view-item"
                :style="{
                    height: itemHeight + 'px'
                }"
                v-for="(item, index) in visibleData" 
                :key="index">
                    {{ item }}
            </li>
        </ul>
  </div>
</template>
<script>
export default {
    name: 'ListView',
    props: {
    data: {
        type: Array,
        default: function() {
            const list = []
            for (let i = 0; i < 1000000; i++) {
                list.push('列表' + i)
            }
            return list
        }
    },
    height: {
        type: Number,
        default: 400
    },
    itemHeight: {
        type: Number,
        default: 30
    },
  },
  computed: {
    contentHeight() {
        return this.data.length * this.itemHeight + 'px';
    }
  },
  mounted() {
      this.updateVisibleData();
  },
  data() {
    return {
      visibleData: []
    };
  },
  methods: {
    updateVisibleData(scrollTop) {
        scrollTop = scrollTop || 0;
        const visibleCount = Math.ceil(this.$el.clientHeight / this.itemHeight);
        const start = Math.floor(scrollTop / this.itemHeight);
        const end = start + visibleCount;
        this.visibleData = this.data.slice(start, end);
        this.$refs.content.style.webkitTransform = `translate3d(0, ${ start * this.itemHeight }px, 0)`;
    },
    updateVisibleData(scrollTop) {
        scrollTop = scrollTop || 0;
        const visibleCount = Math.ceil(this.$el.clientHeight / this.itemHeight); // 取得可見區域的可見列表項數量
        const start = Math.floor(scrollTop / this.itemHeight); // 取得可見區域的起始數據索引
        const end = start + visibleCount; // 取得可見區域的結束數據索引
        this.visibleData = this.data.slice(start, end); // 計算出可見區域對應的數據,讓 Vue.js 更新
        this.$refs.content.style.webkitTransform = `translate3d(0, ${ start * this.itemHeight }px, 0)`; // 把可見區域的 top 設置為起始元素在整個列表中的位置(使用 transform 是為了更好的性能)
    },
    handleScroll() {
        const scrollTop = this.$el.scrollTop;
        this.updateVisibleData(scrollTop);
    }
  }
}
</script>
<style lang="scss" scoped>
.list-view {
    overflow: auto;
    position: relative;
    border: 1px solid #aaa;
    width: 200px;
}
.list-view-phantom {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    z-index: -1;
}
.list-view-content {
    left: 0;
    right: 0;
    top: 0;
    position: absolute;
}
.list-view-item {
    padding: 5px;
    color: #666;
    line-height: 30px;
    box-sizing: border-box;
}
</style>

  


免責聲明!

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



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