uni-app 内嵌小程序虚拟列表实现

手机端的列表页是一个很常见的需求,一般实现起来也不难。但是当这个列表的数据量很大的时候,一直向下查看就会出现卡顿、程序崩溃等情况,究其原因,就是页面渲染了太多的dom占用了手机大量的运存。为了解决这种问题,虚拟列表来了。

pc端很少遇到这种问题是因为产品端的产品形态决定很少有这样的需求,基本都是分页显示,不会出现这种无限瀑布流数据

一、基础实现

虚拟列表的核心就是“只展示当前视口内的内容,其隐藏起来的内容用空的容器撑开高度,保证滚动条的高度一致” 根据这些条件我们来一步步编写实现。

1、找到屏幕最上方显示的第一条数据

export default {
    data () {
        return {
            lineHeight: 44,
            showLine: 0
        }
    },
    onPageScroll (e) {
        this.showLine = Math.round(e.scrollTop/44)
    }
}

2、计算当前设备一屏能展示多少条数据

export default {
    data () {
        return {
            lineHeight: 44, // 每行数据的高度
            showLine: 0, // 当前设备最上面应该展示的数据
            showLineNum: 0, // 当前设备屏幕能展示的数据条数
        }
    },
    onload () {
        let info = uni.getSystemInfoSync()
        this.showLineNum = Math.round(info.windowHeight/44)
    }
}

得到这两组数据后,我们很容易就能计算出当前需要展示的数据索引范围,配上测试的dom结构如下

<template>
    <view class="page">
        <view v-for="item in 100000" :key="item" class="line" style="height: 44px">
            <template v-if="item > showLine && item < showLine + showLineNum">
                <text>姓名</text> <text>年龄</text> <text>性别</text> <text>其他。。。</text>
            </template>
        </view>
    </view>
</template>

这样子就得到了一版虚列表的实现。但是呢,经过测试发现这个代码理论可行,真实环境还是有问题:

快速滚动的时候,上下是会出现空白区域的。并且滚动速度越块,空白区域就越大。

出现这个问题不难理解,设备的性能都是不同的,也就是插入和删除dom的速度是不一样的,页面滚动后dom没有及时的插入或者插入有延迟,就造成了短暂的空白区域。那如果我们给它一个上下的缓冲区,让这个空白区域出现在我们看不到的地方就可以了。

onPageScroll (e) {
    let showLine =  Math.round(e.scrollTop/44)
    this.showLine = showLine > 20 ? 0 : showLine - 20
}

我们把滚动的高度减去缓冲区的高度,这样子就能提前渲染20条数据,解决上半部分的空白缓冲问题;

<template>
    <!--设置20条的缓冲区-->
    <view class="page">
        <view v-for="item in 100000" :key="item" class="line" style="height: 44px">
            <template v-if="item > showLine && item < showLine + showLineNum + 20">
                <text>姓名</text> <text>年龄</text> <text>性别</text> <text>其他。。。</text>
            </template>
        </view>
    </view>
</template>

向下在增加20条数据的渲染,解决下面的空白缓冲区问题。

经过测试和调整,加大了缓冲区的长度到100条,在大部分手机设备的表现还是可以的,在老旧的安卓设备上依旧会出现空白的现象,效果不是很理想,继续优化。

二、优化升级
分析:

虽然增加了缓冲区,但是效果依然不理想。那么只能继续降低性能开销,考虑到每滚动44 就要触发一次删除和添加dom的操作,我们可以从这里入手,降低频率,比如滚动10条的距离再去操作,这样子性能开销肯定会降低很多,而且这样也符合前后端数据传输的分页逻辑。

1、行改页
<template>
    <!--设置2页的缓冲区-->
    <view class="page">
        <view v-for="item in 100000" :key="item" class="line" style="height: 44px">
            <template v-if="getPage(item) > (showPage > 2 ? showPage -2 : showPage) && getPage(item) < showPage + 2">
                <text>姓名</text> <text>年龄</text> <text>性别</text> <text>其他。。。</text>
            </template>
        </view>
    </view>
</template>

<script>
export default {
    data () {
        return {
            lineHeight: 44, // 每行数据的高度
            showPage: 1, // 当前设备最上面应该展示的数据
        }
    },
    methods: {
        getPage (index) {
            return Math.ceil(index / 100)
        }
    },
    onPageScroll (e) {
        this.showPage = Math.floor(e.scrollTop/4400) + 1
    }
}
</script>

我们设置了上下两页的缓冲区,每页按100条数据来计算,我们始终最多显示5页数据即500条,上下缓冲区为8800像素,每滚动4400像素产生一次dom操作,比之前的方案提升了不少的性能;

2、缓存数据

我们解决了dom的渲染问题,让它始终只渲染500条,但是如果我一直往下翻动,加载数据达到了5万条,那么有个问题就是我们从后端请求的数据会占用大量内存,导致程序崩溃。为了解决这个问题 我使用session存储

savePage (page, list) {
    let value = JSON.stringify(list)
	sessionStorage.setItem(`page${page}`, value);
},
getPage (page) {
    return JSON.parse(sessionStorage.getItem(`page${page}`)) 
}

在隐藏dom的同时把那一页的数据也置为null, 这样子就解决数据量大的问题了。

三、总结和问题:

经测试,在大部分手机上表现理想,有部分安卓手机滑动速度异常的快,轻轻一滑就能滚动上万像素的距离(ios系统无此问题),然后还是会出现白屏的情况,经过研究,是因为手机有滚动阻尼禁止滚动惯性的配置参数,不同系统、不同安卓版本参数的大小都不一样。这个api 小程序端是不支持自定义配置的,所以使用参数异常的手机浏览京东/天猫小程序等产品也会发现白屏的现象。所以目前的代码算是实现了虚拟列表功能。

本文的代码只是伪代码,只提供程序的实现方向和思路,实际开发过程中会很多细节问题并没有一一列举。参照本文实现具体代码时,有任何问题可私信协助解决。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值