页面中长列表滚动的优化

栏目: IOS · 发布时间: 5年前

内容简介:导语: 当我们有10万条或者更多的数据需要展示在页面中,会出现哪些问题呢,我们应该怎样处理和优化呢?当我们有10万条或者更多的数据需要展示在页面中,我们应该怎样处理呢?这里主要的方式有两种:

导语: 当我们有10万条或者更多的数据需要展示在页面中,会出现哪些问题呢,我们应该怎样处理和优化呢?

当我们有10万条或者更多的数据需要展示在页面中,我们应该怎样处理呢?

这里主要的方式有两种:

  1. 懒加载:即监听scroll事件或使用 IntersecionObserver 监听;

  2. 可视区域的渲染:仅在可视区域展示数据,为保证滚动条的完整性,非可视区域使用占位元素的高度后者容器的位移来撑开。

1. 懒加载

懒加载的方式有两种:监听scroll事件或使用 IntersecionObserver 监听某个元素。

1.1 scroll事件

监听滚动事件应该是我们使用的最多的事件了,当滚动条滑动到页面最底部或者将要滑动到最底部的时候,去加载下一页的数据。同时图片懒加载或者其他组件的懒加载也可以依赖于滚动事件!

为了优化滚动事件,我们也会给滚动事件添加上 防抖和节流

这是一个很常见的demo,无限加载数据系列,当滚动条滑动到底部时,加载下一页的数据: 监听滚动事件懒加载数据

请分别用微信和QQ扫下这个二维码体验一下,体验时,请点击“向左”的按钮关闭代码的部分:

页面中长列表滚动的优化

其实我们发现会有一个很有意思的体验:在android中的微信和QQ中基本可以无限的滑动,在iOS的微信中也可以,但是在iOS的QQ中,当我们一直滑动时,需要滑动到底部,然后才能加载新的数据,才能继续滑动!

这种原因是iOS不同的webview造成的。在iOS中,UIWebView是在iOS2中出现的,性能更好的WKWebView跟随iOS8一起出现。从性能方面来说,WKWebView会比UIWebView高很多,可以算是一次飞跃。它采用了跨进程的方案,用 Nitro JS 解析器,高达 60fps 的刷新率。同时,提供了很好的H5页面支持,类比UIWebView还多提供了一个加载进度的属性。同时,在UIWebView中,无论是页面的body滚动,还是div级别的局部滚动,都不会实时触发,而是在滚动结束后才会触发!

可以分别体验下面的两个二维码,左侧的为body级别的滚动,右侧为div的局部滚动:

页面中长列表滚动的优化 页面中长列表滚动的优化

总结下滚动事件在不同的webview中的表现情况:

机型 body滚动 局部滚动
iOS WKWebView 实时触发 实时触发
iOS UIWebView 无法实时触发 无法实时触发
Android 实时触发 实时触发

那么该如何优化滚动的性能呢?

首先就是防抖和节流。在Chrome浏览器中使用 performance.now() 测试滚动每次触发的间隔,可以看到,频率大概在16ms左右,但很多时候我们并不需要这么快地触发我们的监听函数,而且,比如图片懒加载等场景,也要及时的更新一次,这时候就用到了防抖和节流!

再有就是使用 requestAnimationFramerequestIdleCallback 代替定时器。若使用定时器进行间歇性触发监听函数,会出现掉帧的情况,我们也知道 setTimeout(fn, 100) ,不一定是正好在100ms后触发fn函数,而是等浏览器空闲之后才调用fn函数,当多次积累后就会产生掉帧的,那么使用 requestAnimationFramerequestIdleCallback ,则浏览器会根据自己渲染的频率适时地执行回调。在追求高性能的渲染效果时,可以考虑用requestIdleCallback()和requestAnimationFrame()代替定时器。前者适合流畅的动画效果场景,后者适用于分离一些优先级低的操作逻辑,使用时需要考虑清楚

1.2 IntersecionObserver

使用IntersecionObserver也可以实现无限滚动,比如在底部监听一个透明的元素,当该元素可见时就加载新的资源!

demo: IntersecionObserver实现无限滚动

不过也应当看到的是IntersecionObserver的支持程度还不太好,如果要使用的话,还是需要polyfill方案!

页面中长列表滚动的优化

而且IntersecionObserver在图片懒加载和组件懒加载的过程中,非常有用,我们后续会进行了解!

2. 模拟滚动

模拟滚动最代表的例子就是 iScroll ,监听手势的touchmove事件,然后使用CSS3中的transform产生位移。

不过在模拟滚动的过程中,若图片比较多时,可以感觉到滚动时有明显的卡顿感。查看这个样例: https://www.xiabingbao.com/demos/20190423/iscroll-scroll.html

这是利用IScroll中的 scroll 事件,无限地向页面中添加元素。不过当页面的数据是无限加载时,应当使用 iscroll-infinite 这个类库更好,这部分留到后面讲解!

3. 可视区域数据的渲染

上面的几种方法里,我们都是无限地直接往页面中添加元素,滑动的页码越大,页面中的DOM元素也就越多。但实际上,这是没有必要的,滚动到可视区域外的元素,用户是看不到的,也没不要保留在页面中,可以用一个占位元素或者transform撑开上面所有不可见元素的高度,让滚动条能够正常的上下滑动即可!

撑开上半部分不可见区域的高度,有两种方法,一种是在容器内的最顶部设置一个占位元素,这个占位元素的高度就是消失的所有DOM的高度;再一个就是给容器或者占位元素一个transform的位移!这里我们使用给占位元素一个高度撑起顶部的滚动区域。

3.1 固定高度的item

若每个item的height/margin/padding等数据都是固定写死的,这种情况比较容易实现!获取一次数据后,不用每次都重新计算。

每次滚动时,都要计算应当渲染列表的哪部分! start 表示列表开始的位置, fixedScrollTop 表示顶部占位元素的距离

// 滚动处理函数
function handleScroller() {
    let lastStart = 0; // 上次开始的位置
    const item = document.querySelector('.container .item');
    const itemStyle = getComputedStyle(item);
    const itemHeight = item.offsetHeight + parseInt(itemStyle['marginTop']) + parseInt(itemStyle['marginBottom']);

    return function() {
        const currentScrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
        const fixedScrollTop = currentScrollTop - currentScrollTop % itemHeight;
        let start = Math.floor( currentScrollTop/itemHeight ); // 可视区域开始渲染的位置

        if (lastStart!==start) {
            lastStart = start;
            createDom(start, count, fixedScrollTop);
        }
    }
}

设置顶部滚动的高度:

function createDom(start, count, height) {
    const container = document.querySelector('.container');
    // container.style.transform = `translateY(${height}px)`; // 给容易一个位移

    let div = document.createDocumentFragment();
    
    // 创建占位元素
    if (height) {
        let p = document.createElement('p');
        p.style.height = height + 'px';
        div.appendChild(p);
    }

    for(let i=start, len=start+count; i<len; i++) {
        let item = document.createElement('div');
        item.className = 'item';
        item.innerHTML = i;
        div.appendChild(item);
    }

    // 为了方便处理,我们这里采用了更新container中的全部元素
    // 你也可以尝试只增加/删除首位的元素,中间元素不变
    container.innerHTML = '';
    container.appendChild(div);
}

可以点击链接查看样例: 可视区域渲染之DOM元素增减 ,审查元素可以看得更清晰,无论我们怎么滚动, .container 中永远只有那么几个元素!

3.2 每个item的高度都不一定

待定,有个问题一直搞不定

3.3 DOM的复用

重点来了,在上面的章节里,我们进行了进一步的优化,只渲染可视区域内的数据。但是还是存在一个重要的问题: 频繁的改动DOM ,这样会频繁的引起页面的重绘。这里我们的思想是对页面中DOM元素进行复用,如下图中所示,从上面滑出的元素,可以直接定位下面再重新装填元素,反之亦然!

页面中长列表滚动的优化

可以点击链接查看demo,在审查元素中我们可以看到,在滚动的过程中,DOM并没有被删掉,而是改变了transform,放到了最下面: 可视区域渲染之DOM元素复用

// 更新页面中DOM元素的位置
function updateDom(start, count, itemHeight, height) {
    document.querySelector('.container .content').style.transform = 'translateY('+height+'px)';
    for (var i = start, len=start+count; i < len; i++) {
        var index = i % count;
        var cssIndex = (i-start) % len;
        document.querySelector('.item' + index).innerHTML = i;
        document.querySelector('.item' + index).style.transform = 'translateY('+itemHeight*cssIndex+'px)';
    }
}

这里我们还是监听了元素的scroll事件,我们在上面一笔带过的 iScroll-infinite 类库,是使用了模拟滚动+DOM复用。点击链接查看demo: iScroll-infinite实现的无限加载

4. 总结

最有效的方案应当是模拟滚动+DOM复用,既解决了滚动不能实时生效的问题,又能解决页面中DOM元素过多,造成滚动中卡顿的问题。

总是想着要讲的有很多,不过最后好像哪方面都欠缺了一点,后面有机会再针对其中的某一个点再深入进行探讨!例如使用 IntersecionObserver 实现Vue组件的懒加载,如何自己实现一个简单的模拟滚动等。

参考:

http://taobaofed.org/blog/2017/03/02/thinking-in-request-animation-frame

http://wiki.jikexueyuan.com/project/iscroll-5/customevents.html


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

操作系统概念(第六版)

操作系统概念(第六版)

(美)西尔伯斯查兹 / 郑扣根 / 高等教育出版社 / 2005-11 / 55.00元

《操作系统概念》(第6版翻译版)是讨论了操作系统中的基本概念和算法,并对大量实例(如Linux系统)进行了研究。全书内容共分七部分。第一部分概要解释了操作系统是什么、做什么、是怎样设计与构造的,也解释了操作系统概念是如何发展起来的,操作系统的公共特性是什么。第二部分进程管理描述了作为现代操作系统核心的进程以及并发的概念。第三部分存储管理描述了存储管理的经典结构与算法以及不同的存储管理方案。第四部分......一起来看看 《操作系统概念(第六版)》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具