列表首屏毫秒级加载与自动滚动定位方案
引用自 摸鱼wiki
场景
<template><div ref="commentsRef"><divv-for="comment in displayComments":key="comment.id":data-cell-id="comment.id"class="card">{{ comment.data }}</div></div>
</template><script lang="ts" setup>
// 假设comment有2000条数据,首次只加载200条
const comments = ref<{ id: string; data: string }[]>();
const displayComments = computed(() => comments.slice(0, 200));
</script>
说明:需要一个场景,用户首屏需要渲染200条数据,且后续如果comments列表发生变动时,页面上能够保持当前显示的条目不进行滚动。
分批加载
如果直接全量数据加载,200个节点肯定不能在1帧内渲染完毕,这样会出现较长时间的白屏。这时候可以使用时间分片的思路,在每个小时间段内渲染少量节点,提高首屏的渲染效率。
<template><div><divv-for="comment in displayComments.slice(0, loadCount)":key="comment.id">{{ comment.data }}</div></div>
</template><script lang="ts" setup>
// 假设comment有2000条数据,首次只加载200条
const commentsRef = ref();
const comments = ref<{ id: string; data: string }[]>();
const displayComments = computed(() => comments.slice(0, 200));// 首屏动态加载的数量
const loadCount = ref<number>(0);
// 如果少于20条,直接全量加载;否则每隔4ms渲染40条数据
watch(displayComments, () => {clearInterval(timer);loadCount.value = 20;if (loadCount.value < nv.length) {timer = setInterval(() => {loadCount.value = Math.min(loadCount.value + 40, nv.length);if (loadCount.value >= nv.length) {clearInterval(timer);}}, 4);}
})
</script>
滚动定位
实现在列表数据更新时自动定位到用户当前可视区域,尽可能保证可视区域的内容不因数据更新而发生剧烈抖动。
获取当前可视内容
通过浏览器提供的 IntersectionObserver API 监听列表中的元素,获取元素滚动时在可视区域内的元素,记录他们的id。
const visibleCardIds: Set<string> = new Set();
const intersection: IntersectionObserver = new IntersectionObserver((entries) => {entries.forEach((e) => {const id = e.target.getAttribute('data-cell-id');if (!id) return;if (e.isIntersecting && e.intersectionRatio > 0.91) {visibleCardIds.add(id);} else {visibleCardIds.delete(id);}});},{threshold: [0, 0.9, 1],},
);const addIntersectionObserve = () => {visibleCardIds.clear();intersection?.disconnect();nextTick(() => {const nodes = document.body.querySelectorAll('.card') || [];for (const node of nodes) {intersection?.observe(node);}});
};
滚动处理
从上一次拿到的可视区域元素中拿到在更新后的数据中仍存在的、最靠近顶部的节点,记录它的位置,并把容器节点滚动到该节点的 scrollTop 高度。
const getNearestVisibleId = () => {return displayComment.value.find(c => visibleCardIds.has(c.id));
}const silenceScroll = () => {const id = getNearestVisibleId();nextTick(() => {commentScrollTop(id);});
};const commentScrollTop = (id: string) => {const n = commentsRef.value?.querySelector(`[data-cell-id="${id}"]`,) as HTMLElement;if (n) {commentsRef.value?.scrollTo({top: n.offsetTop,behavior: 'instant',});}
};
结合起来
在数据更新后的下一帧触发滚动即可实现定位效果
<template><div><divv-for="comment in displayComments.slice(0, loadCount)":key="comment.id">{{ comment.data }}</div></div>
</template><script lang="ts" setup>
// 假设comment有2000条数据,首次只加载200条
const commentsRef = ref();
const comments = ref<{ id: string; data: string }[]>();
const displayComments = computed(() => comments.slice(0, 200));// 首屏动态加载的数量
const loadCount = ref<number>(0);
// 如果少于20条,直接全量加载;否则每隔4ms渲染40条数据
watch(displayComments, () => {clearInterval(timer);loadCount.value = 20;if (loadCount.value < nv.length) {timer = setInterval(() => {loadCount.value = Math.min(loadCount.value + 40, nv.length);if (loadCount.value >= nv.length) {clearInterval(timer);silenceScroll();addIntersectionObserve();}}, 4);} else {silenceScroll();addIntersectionObserve();}
})
</script>
定位优化
首屏加载速度上去了,也实现了自动滚动到指定的条目位置。但这时候遇到个新问题,如果列表的数据发生了变动,比如从前面插入了一条新数据,那么这时候可视范围内的数据肯定会发生改变。
如果使用批量重绘,即使本次修改只有一条数据更新,会出现页面先重新渲染,等10+ms后再滚动到指定位置,中间会出现画面的闪动。
这时候可以利用vue VDom算法的一些小Trick。先判断前后数据的差异程度,如果差异数量小于一个阈值(比如小于10),那么可以借用vue的diff算法,尽可能保留原有节点,渲染少量新节点。这时候可以大大提升页面的渲染效率,即使全量渲染也不会有性能问题。
<template><div><divv-for="comment in displayComments.slice(0, loadCount)":key="comment.id">{{ comment.data }}</div></div>
</template><script lang="ts" setup>
// 假设comment有2000条数据,首次只加载200条
const commentsRef = ref();
const comments = ref<{ id: string; data: string }[]>();
const displayComments = computed(() => comments.slice(0, 200));// 首屏动态加载的数量
const loadCount = ref<number>(0);
// 如果少于20条,直接全量加载;否则每隔4ms渲染40条数据
watch(displayComments, () => {clearInterval(timer);const nSet = new Set(nv.map((v) => v.id));const oSet = new Set(ov.map((v) => v.id));const n = new Set([...nSet].filter((x) => !oSet.has(x)));if (n.size <= 10) {loadCount.value = nv.length;silenceScroll();addIntersectionObserve();} else {loadCount.value = 20;if (loadCount.value < nv.length) {timer = setInterval(() => {loadCount.value = Math.min(loadCount.value + 40, nv.length);if (loadCount.value >= nv.length) {clearInterval(timer);silenceScroll();addIntersectionObserve();}}, 4);} else {silenceScroll();addIntersectionObserve();}}
})
</script>
相关文章:
列表首屏毫秒级加载与自动滚动定位方案
引用自 摸鱼wiki 场景 <template><div ref"commentsRef"><divv-for"comment in displayComments":key"comment.id":data-cell-id"comment.id"class"card">{{ comment.data }}</div></div> &…...

小区物业业主管理信息系统设计的设计与实现(论文+源码)_kaic
摘 要 随着互联网的发展,网络技术的发展变得极其重要,所以依靠计算机处理业务成为了一种社会普遍的现状。管理方式也自然而然的向着现代化技术方向而改变,所以纯人工管理方式在越来越完善的现代化管理技术的比较之下也就显得过于繁琐&#x…...

Fortran 微分方程求解 --ODEPACK
最近涉及到使用Fortran对微分方程求解,我们知道MATLAB已有内置的函数,比如ode家族,ode15s,对应着不同的求解办法。通过查看odepack的官方文档,我尝试使用了dlsode求解刚性和非刚性常微分方程组。 首先是github网址&am…...

8路光栅尺磁栅尺编码器或16路高速DI脉冲信号转Modbus TCP网络模块 YL99-RJ45
特点: ● 光栅尺磁栅尺解码转换成标准Modbus TCP协议 ● 高速光栅尺磁栅尺4倍频计数,频率可达5MHz ● 模块可以输出5V的电源给光栅尺或传感器供电 ● 支持8个光栅尺同时计数,可识别正反转 ● 可以设置作为16路独立DI高速计数器 ● 可网…...
【Python】函数
None类型 思考:若函数没有使用return语句返回数据,那么函数有返回值吗? 答:实际上是有的,Python中有一个特殊的字面量None,其类型是<class ‘NoneType’>,无返回值的函数,实…...

centos安装MySQL 解压版完整教程(按步骤傻瓜式安装
一、卸载系统自带的 Mariadb 查看: rpm -qa|grep mariadb 卸载: rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64 二、卸载 etc 目录下的 my.cnf 文件 rm -rf /etc/my.cnf 三、检查MySQL是否存在 有则先删除 #卸载mysql服务以及删除所有mysql目录 #没…...

【后端速成 Vue】第一个 Vue 程序
1、为什么要学习 Vue? 为什么使用 Vue? 回想之前,前后端交互的时候,前端收到后端响应的数据,接着将数据渲染到页面上,之前使用的是 JavaScript 或者 基于 JavaScript 的 Jquery,但是这两个用起来还是不太…...
Macbook pro M1 安装Ubuntu教程
先讲下心路历程 由于版主最近刚切换到Mac,所以在安装的时候一上手就选择了virutalbox,结果报错“The installer has detected an unsupported architecture. VirtualBox only runs on the amd64 architecture.” 后来去Reddit论坛上一看,才知…...

前端console.log打印内容与后端请求返回数据不一致
后端传值num0 前端打印num1 ,如图,console.log后台显示的数据与展开后不一致 造成该问题原因是深拷贝与浅拷贝的问题。 var obj JSON.parse(JSON.stringify(res)) 修改后打印 正常...
SQL入门:多表查询
SQL,或者说结构化查询语言(Structured Query Language),是用于管理和操作关系型数据库的标准语言。在本篇文章中,我们将重点介绍SQL中的多表查询,这是一种强大的工具,可以帮助我们从多个相关的表格中获取数据。 数据库…...

【C++】进一步认识模板
🏖️作者:malloc不出对象 ⛺专栏:C的学习之路 👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈 目录 前言一、非类型模板参数二、模板的特…...
Mysql Oracle 区别
1. oracle select *, id需要在星号前加别名,mysql则不需要 mysql语法: select *, id from xin_student_t;oracle语法: select st.*, st.id from xin_student_t st;2. oracle表定义了别名,在查询时可以不用别名指定字段…...

华为OD-第K长的连续字母字符串长度
题目描述 给定一个字符串,只包含大写字母,求在包含同一字母的子串中,长度第 k 长的子串的长度,相同字母只取最长的那个子串。 代码实现 # coding:utf-8 # 第K长的连续字母字符串长度 # https://www.nowcoder.com/discuss/353150…...
【编程题】有效三角形的个数
文章目录 一、题目二、算法讲解三、题目链接四、补充 一、题目 给定一个包含非负整数的数组 nums ,返回其中可以组成三角形三条边的三元组个数。 示例1: 输入: nums [2,2,3,4] 输出: 3 **解释:**有效的组合是: 2,3,4 (使用第一个 2) 2,3,4 (使用第二个 …...

【mysql是怎样运行的】-EXPLAIN详解
文章目录 1.基本语法2. EXPLAIN各列作用1. table2. id3. select_type4. partitions5. type 1.基本语法 EXPLAIN SELECT select_options #或者 DESCRIBE SELECT select_optionsEXPLAIN 语句输出的各个列的作用如下: 列名描述id在一个大的查询语句中每个SELECT关键…...
数据结构例题代码及其讲解-链表
链表 单链表的结构体定义及其初始化。 typedef struct LNode {int data;struct LNode* next; }LNode, *LinkList;①强调结点 LNode *p; ②强调链表 LinkList p; //初始化 LNode* initList() {//定义头结点LNode* L (LNode*)malloc(sizeof(LNode));L->next NULL;return …...

[Open-source tool] 可搭配PHP和SQL的表單開源工具_Form tools(1):簡介和建置
Form tools是一套可搭配PHP和SQL的表單開源工具,可讓開發者靈活運用,同時其有數個表單模板和應用模組供挑選,方便且彈性。Form tools已開發超過20年,為不同領域的需求者或開發者提供一個自由和開放的平台,使他們可建構…...
移动数据业务价值链的整合
3G 时代移动数据业务开发体系的建立和发展,要求运营商从封闭、统一的业 务形态、单一提供业务,向开放的、个性化多元化的业务体系以及多方合作参与提 供业务的方向发展,不可避免的使通信价值链不断延长和升级,内容提供商、服务 …...

合并两个链表
题目描述 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 比如以下例子: 题目接口: /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListN…...

测试框架pytest教程(9)跳过测试skip和xfail
skip无条件跳过 使用装饰器 pytest.mark.skip(reason"no way of currently testing this") def test_example(faker):print("nihao")print(faker.words()) 方法内部调用 满足条件时跳过 def test_example():a1if a>0:pytest.skip("unsupported …...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
k8s从入门到放弃之Ingress七层负载
k8s从入门到放弃之Ingress七层负载 在Kubernetes(简称K8s)中,Ingress是一个API对象,它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress,你可…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
Git常用命令完全指南:从入门到精通
Git常用命令完全指南:从入门到精通 一、基础配置命令 1. 用户信息配置 # 设置全局用户名 git config --global user.name "你的名字"# 设置全局邮箱 git config --global user.email "你的邮箱example.com"# 查看所有配置 git config --list…...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合
作者:来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布,Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明,Elastic 作为 …...

消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...