列表首屏毫秒级加载与自动滚动定位方案
引用自 摸鱼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 …...
5分钟解锁WeMod专业版:开源工具让你的游戏修改体验全面升级
5分钟解锁WeMod专业版:开源工具让你的游戏修改体验全面升级 【免费下载链接】Wemod-Patcher WeMod patcher allows you to get some WeMod Pro features absolutely free 项目地址: https://gitcode.com/gh_mirrors/we/Wemod-Patcher 还在为WeMod专业版的订阅…...
光伏储能管理系统:绿虫赋能,破解行业流程痛点
光伏储能产业迎来高速发展期,但其全业务流程的复杂性却成为企业发展的桎梏。从项目开发的多环节审批,到建设阶段的进度质量管控,再到运维结算的数据协同,各环节割裂、部门协作不畅、数据杂乱无章等问题频发,不少企业负…...
基于PLC的智能饲喂系统设计:开启现代养殖自动化新篇章
基于PLC的智能饲喂系统设计 本设计包括设计报告,任务书,模拟工程仿真。本设计的制作智能饲喂是现代物流系统的重要组成部分,是代替人工饲喂的可行性计划,由自动控制与管理系统、配料系统、送料系统、自动统计系统、触摸屏监控系统…...
STS4x温度传感器I²C驱动库深度解析与跨平台移植
1. STS4x温湿度传感器驱动库技术解析1.1 项目定位与工程价值Sensirion STS4x系列是瑞士Sensirion公司推出的高精度数字温度传感器,采用CMOSens技术,具备0.1C典型精度、0.01C分辨率、低功耗(典型待机电流仅0.5μA)及快速响应&#…...
运维提效实战:用 Ansible+Cron 搞定日志自动清理,再也不用半夜爬起来删日志了
前言 作为常年和服务器打交道的运维人,估计没人没经历过半夜被磁盘爆满告警吵醒的崩溃 —— 远程登服务器、挨个找日志文件、手动删旧日志,一套操作下来人彻底清醒,回头还得担心误删关键文件。 其实这类重复又机械的运维活儿,完全…...
1949–2024年中国县级行政区划(逐年)|全国范围、75年连续、SHP格式
🔍 数据简介 本数据集完整覆盖 1949年至2024年 共 76个年份 的中国县级行政区划边界,是目前公开可获取的时间跨度最长、更新粒度最细的全国县级历史区划产品。 每一年份均提供独立、闭合、无重叠的面状矢量边界,属性表包含标准名称、行政区划…...
如何彻底告别微软Edge浏览器:EdgeRemover专业卸载工具完全指南
如何彻底告别微软Edge浏览器:EdgeRemover专业卸载工具完全指南 【免费下载链接】EdgeRemover PowerShell script to remove Microsoft Edge in a non-forceful manner. 项目地址: https://gitcode.com/gh_mirrors/ed/EdgeRemover 你是否曾经尝试卸载Microsof…...
这次终于选对了!盘点2026年圈粉无数的AI论文网站
一天写完毕业论文在2026年已不再是天方夜谭。这是2026年最炸裂、实测能大幅提速的AI论文网站,覆盖选题、写作、查重、排版全流程,真正帮你高效搞定论文。 一、全流程王者:一站式搞定论文全链路(一天定稿首选) 这类工具…...
用过才敢说 AI论文平台测评:2026年最值得尝试的几款工具
2026年真正好用的AI论文平台,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…...
从HBuilder到npm:UniApp项目迁移与打包实战指南
1. 为什么需要从HBuilder迁移到npm? 很多UniApp开发者最初都是通过HBuilder这个集成开发环境入门,毕竟它提供了开箱即用的UniApp开发体验。但随着项目规模扩大,团队协作需求增加,或者需要更灵活的构建配置时,基于npm的…...
