基于用户感知实现无限滚动的 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. 整屏虚拟列表,首页全部内容都在虚拟列表中
相关推荐
_AaronWong13 小时前
Electron 实现仿豆包划词取词功能:从 AI 生成到落地踩坑记
前端·javascript·vue.js
cxxcode13 小时前
I/O 多路复用:从浏览器到 Linux 内核
前端
用户54330814419413 小时前
AI 时代,前端逆向的门槛已经低到离谱 — 以 Upwork 为例
前端
JarvanMo13 小时前
Flutter 版本的 material_ui 已经上架 pub.dev 啦!快来抢先体验吧。
前端
JohnYan13 小时前
工作笔记-CodeBuddy应用探索
javascript·ai编程·aiops
恋猫de小郭14 小时前
AI 可以让 WIFI 实现监控室内人体位置和姿态,无需摄像头?
前端·人工智能·ai编程
哀木14 小时前
给自己整一个 claude code,解锁编程新姿势
前端
程序员鱼皮14 小时前
GitHub 关注突破 2w,我总结了 10 个涨星涨粉技巧!
前端·后端·github
UrbanJazzerati14 小时前
Vue3 父子组件通信完全指南
前端·面试
是一碗螺丝粉14 小时前
5分钟上手LangChain.js:用DeepSeek给你的App加上AI能力
前端·人工智能·langchain