前端面试100道手写题(6)—— 虚拟滚动

简介: 虚拟滚动在前端中是一个很常见的解决方案,由于浏览器对于内存的限制,当页面需要展示大量 DOM 节点(如:列表数据超过 10 万)的时候,如果完整渲染整个 DOM 树,当页面数据需要更新重新渲染的时候就会出现滚动卡顿,这个时候就需要虚拟滚动去模拟浏览器原生滚动事件,从而避免这个卡顿情况。

前言

虚拟滚动在前端中是一个很常见的解决方案,由于浏览器对于内存的限制,当页面需要展示大量 DOM 节点(如:列表数据超过 10 万)的时候,如果完整渲染整个 DOM 树,当页面数据需要更新重新渲染的时候就会出现滚动卡顿,这个时候就需要虚拟滚动去模拟浏览器原生滚动事件,从而避免这个卡顿情况。

手写难度:⭐️⭐️⭐️

涉及知识点:

  • 滚动监听事件 wheel/move
  • 事件节流
  • 滚动偏移量 offset
  • 按需渲染计算方案

虚拟滚动

实现方案

  • 步骤 1: 监听虚拟滚动容器的 wheeltouchmove 事件
  • 步骤 2: 创造子容器用于填充父容器,使得父容器可以滚动
  • 步骤 3: 提供一个渲染子元素 item函数,返回 dom 节点
  • 步骤 4: 计算每个元素的高度,然后计算出总共应该渲染多少个子元素 item
  • 步骤 5: 当发生滚动事件的时候,更新子容器的偏移高度,然后触发 步骤 4

抽象方案

定义一个类Scroll,接收参数为:

  • el 列表容器 DOM 节点
  • list 列表数据
  • itemRender 子元素渲染函数
  • itemHeight 子元素高度

使用例子为:

const scroll = new Scroll({
   
    el: document.getElementById('scroll'),
    list: [],
    itemRender: (item)=>{
   
        let child = document.createElement('div');
        child.innerText = `第${
     item}个div`;
        return child;
    }
});

同时需要支持以下函数:

  • update 列表数组更新, 触发重新渲染

虚拟滚动列表执行步骤:

  1. 构造函数初始化,如:start end 代表位置
  2. bindEvents 监听滚动事件,触发后续渲染render
  3. init 初始化一个内置容器,用来放置子元素,从而不影响父容器的高度,使得父容器可以滚动
  4. render计算容器滚动高度和元素 item 渲染高度,判断应该渲染哪部分元素 item

简易版源码实现:

class Scroll {
   
    constructor({
    el, list, itemRender, itemHeight = 30 }) {
   
        this.$list = el; // 列表容器
        this.list = list;
        this.itemRender = itemRender;
        this.itemHeight = itemHeight;
        this.start = 0;
        this.end = 0;
        this.bindEvents();
        this.init();
    }

    init() {
   
        // 创建一个子容器,用于渲染列表项
        const childContainer = document.createElement('div');
        childContainer.style.position = 'relative';
        childContainer.style.width = '100%';
        childContainer.style.boxSizing = 'border-box';
        childContainer.style.paddingTop = '0px';
        childContainer.style.overflow = 'hidden';
        childContainer.style.height = `${
     this.list.length * this.itemHeight}px`;
        this.$list.appendChild(childContainer);
        this.$child = childContainer;
        this.render();
    }

    bindEvents() {
   
        let y = 0;
        const updateOffset = (e) => {
   
            this.render();
        }
        this.$list.addEventListener('scroll', updateOffset);
    }

    update() {
   
        this.render();
    }

    render() {
   
        const {
    list, itemRender, itemHeight } = this;
        const {
    scrollTop, clientHeight } = this.$list;
        const start = Math.floor(scrollTop / itemHeight);
        const gap = Math.ceil(clientHeight / itemHeight);
        console.log('start', start)
        if (start < 0) {
   
            return;
        }
        let end = start + gap * 2;
        if (start === this.start && end === this.end) {
   
            return;
        }
        if (end > list.length) {
   
            end = list.length;
        }
        // 更新子容器的高度和偏移量
        this.{
   mathJaxContainer[0]}{
   this.list.length * this.itemHeight}px`;
        this.$child.style.paddingTop = `${start * itemHeight}px`;

        this.start = start;
        this.end = end;

        const fragment = document.createDocumentFragment();

        for (let i = start; i < end; i++) {
   
            const item = list[i];
            const $item = itemRender(item);
            {
   mathJaxContainer[2]}{
   itemHeight}px`;
            fragment.appendChild($item);
        }
        this.$child.innerHTML = '';
        this.$child.appendChild(fragment);
    }
}

这样子看起来虚拟滚动是不是十分简单,但是其实有些功能还需要优化,具体如下:

  • 节流触发滚动函数,避免每次滚动都进行更新
  • 列表缓存,减少列表渲染样式更新
  • 提前进行更新渲染,减少因为滚动导致的更新等等

完整代码我放到 github 上,大家感兴趣可以去看看Github Router完整实现

Demo体验可以看这里

参考资料

目录
相关文章
|
1月前
|
前端开发 JavaScript 网络协议
前端最常见的JS面试题大全
【4月更文挑战第3天】前端最常见的JS面试题大全
54 5
|
2月前
|
Web App开发 前端开发
【前端篇】前端实现滚动分屏效果
【前端篇】前端实现滚动分屏效果
58 0
|
4月前
|
设计模式 前端开发 算法
No210.精选前端面试题,享受每天的挑战和学习
No210.精选前端面试题,享受每天的挑战和学习
No210.精选前端面试题,享受每天的挑战和学习
|
4月前
|
消息中间件 缓存 前端开发
No209.精选前端面试题,享受每天的挑战和学习
No209.精选前端面试题,享受每天的挑战和学习
No209.精选前端面试题,享受每天的挑战和学习
|
4月前
|
前端开发 JavaScript Java
No208.精选前端面试题,享受每天的挑战和学习
No208.精选前端面试题,享受每天的挑战和学习
No208.精选前端面试题,享受每天的挑战和学习
|
4月前
|
存储 JSON 前端开发
No206.精选前端面试题,享受每天的挑战和学习
No206.精选前端面试题,享受每天的挑战和学习
No206.精选前端面试题,享受每天的挑战和学习
|
13天前
|
存储 缓存 监控
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
【4月更文挑战第30天】本文探讨了Flutter中优化列表滚动性能的策略。建议使用`ListView.builder`以节省内存,避免一次性渲染所有列表项。为防止列表项重建,可使用`UniqueKey`或`ObjectKey`。缓存已渲染项、减少不必要的重绘和异步加载大数据集也是关键。此外,选择轻量级组件,如`StatelessWidget`,并利用Flutter DevTools监控性能以识别和解决瓶颈。持续测试和调整以提升用户体验。
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
|
13天前
|
前端开发 数据处理
【Flutter 前端技术开发专栏】Flutter 中的滚动性能优化与无限列表实现
【4月更文挑战第30天】本文探讨了 Flutter 中的滚动性能优化和无限列表实现。关键点包括:1) 滚动性能直接影响用户满意度,优化可提升响应速度;2) 影响因素有布局复杂度、频繁重绘和数据处理;3) 优化措施包括懒加载、简化布局、减少不必要的重绘和高效处理数据;4) 无限列表通过监听滚动位置,动态加载新数据;5) 实现时注意加载策略、数据处理效率和内存管理。案例分析和总结强调了优化在实际开发中的重要性。
【Flutter 前端技术开发专栏】Flutter 中的滚动性能优化与无限列表实现
|
13天前
|
前端开发 UED 开发者
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
【4月更文挑战第30天】Flutter开发中,优化列表和滚动视图至关重要。本文介绍了几种优化方法:1) 使用`ListView.builder`和`GridView.builder`实现懒加载;2) 复用子组件以减少实例创建;3) 利用`CustomScrollView`和`Slivers`提升滚动性能;4) 通过`NotificationListener`监听滚动事件;5) 使用`KeepAlive`保持列表项状态。掌握这些技巧能提升应用性能和用户体验。
【Flutter前端技术开发专栏】Flutter中的列表与滚动视图优化
|
2月前
|
存储 缓存 监控
2024年春招小红书前端实习面试题分享
春招已经拉开帷幕啦! 春招的拉开,意味着新一轮的求职大战已经打响,希望每位求职者都能充分准备,以最佳的状态迎接挑战,找到心仪的工作,开启职业生涯的新篇章。祝愿每位求职者都能收获满满,前程似锦!
80 3