IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    如何实现虚拟滚动? · 看不见我的美 · 是你瞎了眼

    馬腊咯稽发表于 2021-12-02 00:00:00
    love 0
    如何实现虚拟滚动?

    首先,需要有一个滚动容器 div 来放置滚动列表 ul;按照通常的渲染方式,ul 里会有大量(成千上万)的列表元素 li;当对某一个 li 进行 DOM 操作时(比如,一些股票软件会通过高亮的方式实时渲染此支股票的涨跌),需要先遍历所有 li,找到要进行操作的元素,再进行 DOM 操作。此时,页面可能会因为 DOM 树过于庞大而占用大量内存,页面渲染可能卡顿。而虚拟滚动要做的,就是在保证用户正常交互体验的同时尽可能少的渲染 DOM,提升页面的响应速度。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    
    <style>
     * {
     margin: 0;
     padding: 0;
     }
     body {
     height: 100vh;
     display: flex;
     justify-content: center;
     align-items: center;
     }
     .container {
     height: 80vh;
     width: 80vw;
     overflow-y: scroll;
     outline: 1px solid orange;
     }
     .list {
     box-sizing: border-box;
     }
     .list li {
     width: 100%;
     height: 36px;
     outline: 1px solid red;
     text-align: center;
     line-height: 36px;
     }
    </style>
    <div class="container">
     <ul class="list">
     <!-- <li>...</li> -->
     <!-- <li>...</li> -->
     <!-- <li>...</li> -->
     <!-- <li>...</li> -->
     <!-- <li>...</li> -->
     <!-- ... -->
     </ul>
    </div>
    

    具体逻辑如下:

    1. 首先计算用户可以看到的元素数量;
    2. 根据滚动条高度,计算从那个数据开始渲染;
    3. 滚动时需要动态计算需要填充的 padding 和数据。
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    
    const listData = Array.from({ length: 3600 }, (v, i) => `第 ${i + 1} 条数据`); // 假数据
    const containerEle = document.querySelector('.container'); // 滚动容器
    const listEle = document.querySelector('.list'); // 滚动列表
    class VirtualScroll {
     /**
     * containerEle 滚动容器
     * listEle 滚动列表
     * listData 列表数据
     * itemHeight 元素高度
     */
     constructor(containerEle, listEle, listData, itemHeight = 36) {
     this.containerEle = containerEle;
     this.listEle = listEle;
     this.listData = listData;
     this.itemHeight = itemHeight;
     this.viewMax = 0; // 可视元素个数
     this.renderIndex = 0; // 渲染起始位置
     this.renderData = []; // 渲染数据
     this.init();
     }
     // 初始化
     init() {
     this.containerEle.scrollTop = 0;
     this.viewMax =
     Math.floor(this.containerEle.clientHeight / this.itemHeight) + 1;
     this.fillRenderData();
     this.renderDOM();
     this.modifyListElePadding();
     this.containerEle.removeEventListener('scroll', this.handleScroll);
     this.containerEle.addEventListener('scroll', this.handleScroll);
     }
     // 填充渲染数据
     fillRenderData() {
     // 仅需要渲染比 this.viewMax 稍多的元素即可(这里取了两倍),不然滚动起来会露馅儿
     this.renderData = this.listData.slice(
     this.renderIndex,
     this.renderIndex + this.viewMax * 2
     );
     }
     // 渲染 DOM
     renderDOM() {
     const fragment = document.createDocumentFragment();
     this.renderData.forEach(item => {
     const liEle = document.createElement('li');
     liEle.textContent = item;
     fragment.appendChild(liEle);
     });
     this.listEle.innerHTML = ''; // 渲染前先清空旧的
     this.listEle.appendChild(fragment);
     }
     // 修改填充高度
     modifyListElePadding() {
     // ul 里除了 li,还需要设置 padding(top/bottom)将 ul 高度撑起来,否则滚动条会露馅儿
     const listElePadding =
     this.itemHeight * (this.listData.length - this.renderData.length); // 总 padding(top + bottom)
     const listElePaddingTop = this.itemHeight * this.renderIndex;
     const listElePaddingBottom = listElePadding - listElePaddingTop;
     this.listEle.style.padding = `${listElePaddingTop}px 0 ${listElePaddingBottom}px`;
     }
     // 处理滚动行为
     handleScroll = e => {
     const scrollTop = e.currentTarget.scrollTop || 0; // 获取滚动高度
     this.renderIndex = Math.floor(scrollTop / this.itemHeight); // 根据滚动高度设置渲染起始位置
     // 重新进行 DOM 填充即可
     this.fillRenderData();
     this.renderDOM();
     this.modifyListElePadding();
     };
    }
    new VirtualScroll(containerEle, listEle, listData);
    

    DEMO 在 这里 。



沪ICP备19023445号-2号
友情链接