基于用户感知实现无限滚动的 Feeds 列表

一、背景

1.1 简介

常规的长列表解决方案,包括无限加载、分页加载或虚拟列表等,但在一些特殊场景下无法用单一的技术解决问题,比如在移动端 H5 页面中在页面最底部有一个无限加载的 feeds 列表,此时使用无限加载、分页加载会有明显的响应度问题,使用虚拟列表会有白屏、滚动嵌套的问题,因此一个无限滚动的 feeds 列表并没有想象的那么简单。

1.2 场景举例

假设有一个移动端的 H5 页面:提供帖子列表功能,其表现形式为 Feeds 流,封装一个【无尽滚动列表】从交互的角度提升用户体验,提升页面的响应度。

其出现位置如下:

1.3 开发挑战

在业务复杂度、交互复杂度、性能体验稳定性要求都很高的今天,一个大的长列表无疑给技术开发带来巨大的挑战,用户滑动页面不断加载 feeds 数据, DOM 节点不断累加创建,因为 DOM 节点过多,导致页面数据更新时卡顿问题。

解决以上问题一般会通过虚拟滚动减少页面 DOM 数量,但虚拟列表自身存在一定问题并非适合所有场景,其存在以下特点:

  1. 用计算机算力和浏览器的渲染能力换取 DOM 数的减少,其最直接的影响是在用户在机器配置较低或者 js 线程较忙时触发滚动,尤其是快速滚动的情况下,因来不及渲染出现白屏
  2. 因滚动容器高度需要参与计算,因此其实现方式往往需要固定容器高度,在首页有前置内容的前提下实际上无法提供固定高度,即便提供了具体高度也会发生滚动嵌套等情况

综上所述,虚拟列表比较适合列表项较多、列表项内容简单和滚动容器高度固定的场景,显然在示例这个场景直接使用虚拟列表并不合适。

为了系统化的解决这个问题,一方面需要对已有的技术进行分析总结,另外一方面需要基于总结并结合实际给出体系化的解决思路并落地。

二、目标和规划

2.1 目标

基于用户感知实现,目标是让用户花在网站上的大多数时间不是等待加载,而是在使用时等待响应,其中的"响应度"不仅仅是实现层面问题,更是个设计问题,仅靠性能优化与更快的硬件是难以解决的。

简而言之,就是不强制打断用户的操作行为,让用户在操作动作进行的同时等待加载

研究人员在过去 50 年里一致发现,一个交互系统的响应度,即能否跟上用户、及时告知当前状态,而不让他们无故等待,是决定用户满意度的最重要的因素。

From: Designing with the Mind in Mind - Jeff Johnson

2.1.1 行业标准

大脑需要多少时间去"感觉"与"认知"?

人们对于延迟和停顿的感知能力也有一定的反应时间。

RAIL 性能模型

Google RAIL 性能模型的目标,就是使用户满意,而不是让页面能运行的很快。

其关键指标内容如下:

2.1.3 短期目标

如果需求属于探索型 需求,其形态可能会随着后期效果和数据观测进行调整。这种情况下对开发工作的影响,要求代码尽量追求张弛有度,求稳求速,无须追求极致完美。

因此 Feeds 列表一期的目标可以为:

  1. 采用分页加载技术为主,以最稳定、稳妥的形式展现列表,降低复杂技术的开发风险
  2. 辅以预加载 + 占位图技术,提升列表响应度,从而提升用户感知,控制用户滚动行为停顿时间小于 100ms

通过预加载提前获取数据,比如首页有50条数据,用户划到30条的时候就开始加载下一页的数据。 通过占位图,实现无阻赛的滚动,即:用户滚动到当前页数据结尾时,或者在预加载开始时,就直接填充下一页列表,只不过是空数据,展现效果为占位符,等接口返回数据时再替换占位符。

2.1.3 长期目标

依实际情况进行调整

分页加载 + 虚拟列表 + 占位图为主的技术形式实现,辅以定制化虚拟列表,从而解决用户滑动页面不断加载 feeds 数据, DOM 节点不断累加创建,因为 DOM 节点过多,导致页面数据更新时卡顿问题。

三、实现细节

3.1 分页加载

列表滚动时"停顿感"的产生

  • 用户滑动

  • 等待数据加载

  • 渲染新数据

  • 用户继续滑动,查看新渲染的内容

"滚动事件触发 -> 异步获取数据 -> 渲染" 这样的机制,页面响应用户操作,并渲染新内容可能需要花费大于500ms的时间。

尽管提供了spinner等友好的反馈提示,但用户切实的感受到了"等待加载"这一过程,而且需要再次滚动,才能浏览新渲染的内容。

3.2 预加载

预加载是常用的优化手段之一,设置一定的阈值,在用户滚动到页面底部前获取数据,加载下一页的数据并渲染。

预加载能有效的提升体验,但是在弱网环境或用户快速滚动时,底部的loading依然会经常性的触发。

而一味的提高提前加载的阈值,肯定不是个好办法,首先是网络状况等情况随时可能改变,导致边界难以确定,其次是必定会产生大量冗余的数据请求。

3.3 无阻塞的滚动

这是一种比较理想的列表运行方式,滚动时永远不打断用户操作,数据不足时根据滚动位置按需加载,先插入占位元素,等到数据获取时再渲染新的元素。

3.4 虚拟列表

尽管这种懒加载的分页加载可以解决网络请求和首屏加载的问题,但随着数据增加,DOM 元素的数量也会不断增加,可能导致页面出现卡顿的情况。

为了解决这个问题,我们可以引入虚拟列表的概念和实现方法。虚拟列表的原理和实现思路已经在网上有很多资料,这里就不再赘述。

虚拟列表的主要目标是解决列表渲染性能问题,并解决随着数据增加而导致的 DOM 元素过多的问题。

3.5 分页加载 + 虚拟列表 + 占位图

  1. 列表组件只关心和渲染限定范围(视窗可见区域+前后预Buffer数量)内的元素。
  2. 每当滚动触发导致元素移出视窗,组件会计算并填充渲染范围内变化的部分,并回收多余的DOM节点。
  3. 如果渲染范围内的元素还未取到所需的数据,则会先渲染占位元素,同时将缺失的数据条数通知给调用方,在异步数据返回后渲染并替换占位元素。

3.6 定制化虚拟列表

探索定制化虚拟列表(分页加载 + 虚拟列表 + 占位图)实现的可能性,实现形式如下:

  1. 固定列表高度,动态开启容器滚动实现
  2. 整屏虚拟列表,首页全部内容都在虚拟列表中
相关推荐
昨天;明天。今天。2 分钟前
案例-任务清单
前端·javascript·css
一丝晨光29 分钟前
C++、Ruby和JavaScript
java·开发语言·javascript·c++·python·c·ruby
Front思30 分钟前
vue使用高德地图
javascript·vue.js·ecmascript
zqx_71 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己1 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称2 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河3 小时前
CSS总结
前端·css
NiNg_1_2343 小时前
Vue3 Pinia持久化存储
开发语言·javascript·ecmascript