虚拟滚动列表基础原理篇

1,829 阅读1分钟

虚拟滚动列表基础原理篇

虚拟滚动列表听说已久,一直没有自己动手实现, 今天剖析一下基础原理,涉及缓存和优化请查看下一篇文章 虚拟滚动列表优化篇,下面直接进入正题。

1. 页面构成

  • 外部固定高度的盒子
  • 内部模拟全部列表元素高度的盒子
  • 真实的显示区域

2. 需要注意问题

  • 真实显示区域就是中间那块有元素的地方
  • 需要让真实显示的区域不会滚动划走
  • 滚动高度不是每个列表元素高度整数倍的处理

3. 具体实现

html 部分, 这里以vue为基础

 <div id="app">
    <!-- 外层盒子,固定高度 -->
    <div class="container" :style="`height: ${boxH}px`" @scroll="handleScroll">
      <!-- 内层滚动盒子, 高度是虚拟的数据的整体高度!!!, 这样使得滚动条更像真实的 -->
      <div class="scroll-box" :style="`height: ${allHeight}px`">
        <!-- 真正显示区域, 需要通过trasform 来让显示区域一直在屏幕中,而不是滑走 -->
        <ul :style="`transform:translateY(${offsetY}px)`">
          <li
            v-for="item,index in nowList" :key="index"
            :style="`height: ${itemH}px`"
            class="list-item"
          >{{item}}</li>
        </ul>
      </div>
    </div>
  </div>

css 部分

<style>
    * {
      padding: 0;
      margin: 0;
    }
    .container {
      overflow-y: auto;
      box-sizing: border-box;
      border: 1px solid black;
    }
    .list-item {
      list-style: none;
      border: 1px solid red;
    }
  </style>

js 部分代码

 <script>
    new Vue({
      el:"#app",
      data() {
        return {
          boxH: 700, // 外层盒子高度
          itemH: 100, // 每个元素的高度
          ListNum: 100, // 整体个数
          list: [], // 列表整体数据
          nowList: [], // 目前显示列表
          offsetY: 0, // 显示区域动态偏移量
        }
      },
      created() {
        // 初始化第一页面的数据
        this.init()
      },
      computed: {
        allHeight() {
          return this.ListNum * this.itemH
        },
        pageNum() {
          return Math.ceil(this.boxH / this.itemH)
        }
      },
      methods: {
        init() {
          // 1. 模拟整个列表元素
          const list = []
          for(let i = 0; i < this.ListNum; i++) {
            list.push(i)
          }
          this.list = list
          // 2. 取得当前第一页的显示数据
          this.nowList = this.list.slice(0, this.pageNum + 1)
        },
        handleScroll(e) {
          // e.target.scrollTop 卷起高度, 需注意滑动单个元素显示一半的情况
          const scrollTop = e.target.scrollTop
          // 1.保持显示区域一直在屏幕上
          this.offsetY = scrollTop - (scrollTop % this.itemH)

          // 2.计算卷起多少个,替换更新
          let startIndex = Math.floor(scrollTop / this.itemH)
          this.nowList = this.list.slice(startIndex, startIndex + this.pageNum)
        }
      }
    })
  </script>

详情请看注释 哈哈哈哈 。