页面中长列表滚动的优化

栏目: 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


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

查看所有标签

猜你喜欢:

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

区块链与人工智能:数字经济新时代

区块链与人工智能:数字经济新时代

高航、俞学劢、王毛路 / 电子工业出版社 / 2018-7-23 / 80

《区块链与人工智能》是畅销书《区块链与新经济:数字货币2.0时代》全新修订升级版。本书是市场上为数不多的系统阐述区块链、人工智能技术与产业的入门级系统教程。从比特币到各类数字货币(代币),从基础原理到应用探讨,全景式呈现区块链与人工智能的发展脉络,既有历史的厚重感也有科技的未来感。本书的另一个亮点是系统整理了区块链创业地图,是一本关于区块链创业、应用、媒体的学习指南,以太坊创始人Vitalik专门......一起来看看 《区块链与人工智能:数字经济新时代》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换