前端时间分片渲染

在经典的面试题中:”如果后端返回了十万条数据要你插入到页面中,你会怎么处理?

除了像 useVirtualList 这样的虚拟列表来处理外,我们还可以通过 时间分片 来处理

通过 setTimeout

直接上一个例子:

 

<!--* @Author: Jolyne* @Date: 2023-09-22 15:45:45* @LastEditTime: 2023-09-22 15:47:24* @LastEditors: Jolyne* @Description: 
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>十万数据渲染</title>
</head><body><ul id="list-container"></ul><script>const oListContainer = document.getElementById('list-container')const fetchData = () => {return new Promise(resolve => {const response = {code: 0,msg: 'success',data: [],}for (let i = 0; i < 100000; i++) {response.data.push(`content-${i + 1}`)}setTimeout(() => {resolve(response)}, 100)})}// 模拟请求后端接口返回十万条数据// 渲染 total 条数据中的第 page 页,每页 pageCount 条数据const renderData = (data, total, page, pageCount) => {// base case -- total 为 0 时没有数据要渲染 不再递归调用if (total <= 0) return// total 比 pageCount 少时只渲染 total 条数据pageCount = Math.min(pageCount, total)setTimeout(() => {const startIdx = page * pageCountconst endIdx = startIdx + pageCountconst dataList = data.slice(startIdx, endIdx)// 将 pageCount 条数据插入到容器中for (let i = 0; i < pageCount; i++) {const oItem = document.createElement('li')oItem.innerText = dataList[i]oListContainer.appendChild(oItem)}renderData(data, total - pageCount, page + 1, pageCount)}, 0)}fetchData().then(res => {renderData(res.data, res.data.length, 0, 200)})</script>
</body></html>

上面的例子中,我们使用了 setTimeout,在每一次宏任务中插入一页数据,然后设置多个这样地宏任务,直到把所有数据都插入为止。

但是很明显能看到的问题是,快速拖动滚动条时,数据列表中会有闪烁的情况

这是因为:

当使用 setTimeout 来拆分大量的 DOM 插入操作时,虽然我们将延迟时间设置为 0ms,但实际上由于 JavaScript 是单线程的,任务执行时会被放入到事件队列中,而事件队列中的任务需要等待当前任务执行完成后才能执行。所以即使设置了 0ms 延迟,setTimeout 的回调函数也不一定会立即执行,可能会受到其他任务的阻塞。

当 setTimeout 的回调函数执行的间隔超过了浏览器每帧更新的时间间隔(一般是 16.7ms),就会出现丢帧现象。丢帧指的是浏览器在更新页面时,没有足够的时间执行全部的任务,导致部分任务被跳过,从而导致页面渲染不连续,出现闪烁的情况

所以,我们改善一下,通过 requestAnimationFrame 来处理

通过 requestAnimationFrame

<!--* @Author: Jolyne* @Date: 2023-09-22 15:45:45* @LastEditTime: 2023-09-22 15:47:24* @LastEditors: Jolyne* @Description: 
-->
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>直接插入十万条数据</title>
</head><body><ul id="list-container"></ul><script>const oListContainer = document.getElementById('list-container')const fetchData = () => {return new Promise(resolve => {const response = {code: 0,msg: 'success',data: [],}for (let i = 0; i < 100000; i++) {response.data.push(`content-${i + 1}`)}setTimeout(() => {resolve(response)}, 100)})}// 模拟请求后端接口返回十万条数据// 渲染 total 条数据中的第 page 页,每页 pageCount 条数据const renderData = (data, total, page, pageCount) => {// base case -- total 为 0 时没有数据要渲染 不再递归调用if (total <= 0) return// total 比 pageCount 少时只渲染 total 条数据pageCount = Math.min(pageCount, total)requestAnimationFrame(() => {const startIdx = page * pageCountconst endIdx = startIdx + pageCountconst dataList = data.slice(startIdx, endIdx)// 将 pageCount 条数据插入到容器中for (let i = 0; i < pageCount; i++) {const oItem = document.createElement('li')oItem.innerText = dataList[i]oListContainer.appendChild(oItem)}renderData(data, total - pageCount, page + 1, pageCount)})}fetchData().then(res => {renderData(res.data, res.data.length, 0, 200)})</script>
</body></html>

 

很明显,闪烁的问题被解决了

这是因为:

requestAnimationFrame 会在浏览器每次进行页面渲染时执行回调函数,保证了每次任务的执行间隔是稳定的,避免了丢帧现象。所以在处理大量 DOM 插入操作时,推荐使用 requestAnimationFrame 来拆分任务,以获得更流畅的渲染效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/170110.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于ARM+FPGA+AD的多通道精密数据采集仪方案

XM 系列具备了数据采集仪应具备的“操作简单、便于携带、满足各种测量需求”等功能的产品。具有超小、超轻量的手掌大小尺寸&#xff0c;支持8 种测量模块&#xff0c;还可进行最多576 Ch的多通道测量。另外&#xff0c;支持省配线系统&#xff0c;可大幅削减配线工时。使用时不…

LSM Tree 深度解析

我们将深入探讨日志结构合并树&#xff0c;也称为LSM Tree&#xff1a;这是许多高度可扩展的NoSQL分布式键值型数据库的基础数据结构&#xff0c;例如Amazon的DynamoDB、Cassandra和ScyllaDB。这些数据库的设计被认为支持比传统关系数据库更高的写入速率。我们将看到LSM Tree如…

C++:哈希

目录 一、unordered系列关联容器 二、底层的结构 哈希结构 哈希冲突/哈希碰撞 ①、闭散列 —> 开放定址法 闭散列的模拟实现 ②、开散列 —> 拉链法/哈希桶 哈希桶的模拟实现 三、哈希应用 位图 位图的特点 位图的模拟实现 布隆过滤器 布隆过滤器的模拟实现…

Lua与C++交互

文章目录 1、Lua和C交互2、基础练习2.1、加载Lua脚本并传递参数2.2、加载脚本到stable&#xff08;包&#xff09;2.3、Lua调用c语言接口2.4、Lua实现面向对象2.5、向脚本中注册c的类 1、Lua和C交互 1、lua和c交互机制是基于一个虚拟栈&#xff0c;C和lua之间的所有数据交互都通…

SYS/BIOS 开发教程: 创建自定义平台

目录 SYS/BIOS 开发教程: 创建自定义平台创建自定义平台新建工程并指定自定义平台修改现有工程使用自定义平台 参考: TI SYS/BIOS v6.35 Real-time Operating System User’s Guide 6.2节 本示例基于 EVMC6678L 开发板, 创建自定义平台, 并将代码段的位置指定到C6678器件内部的…

安卓主板,人脸识别主板考勤门禁智能门锁安卓主板开发方案

人脸识别主板是一种广泛应用于多个领域的技术&#xff0c;包括人脸支付系统、人脸识别监控系统、写字楼办公楼门禁闸机、校园、地铁、住宅门禁、考勤机、智能门锁、广告机、售卖机以及其他行业应用设计等。这些主板基于联发科MTK方案&#xff0c;由行业PCBA和MTK的核心板组成。…

搭建zlmediakit和wvp_pro

zlmediakit使用zlmediakit/zlmediakit:master镜像 wvp_pro使用648540858/wvp_pro&#xff0c;可参照https://github.com/648540858/wvp-GB28181-pro wvp_pro官方https://doc.wvp-pro.cn/#/ 刚开始我找了个docker镜像运行&#xff0c;后来播放页面一直加载&#xff0c;最后就用了…

Windows下Eclipse C/C++开发环境配置教程

1.下载安装Eclipse 官网下载eclipse-installer&#xff08;eclipse下载器&#xff09;&#xff0c;或者官方下载对应版本zip。 本文示例&#xff1a; Eclipse IDE for C/C Developers Eclipse Packages | The Eclipse Foundation - home to a global community, the Eclipse ID…

自动化测试07Selenium01

目录 什么是自动化测试 Selenium介绍 Selenium是什么 Selenium特点 工作原理 SeleniumJava环境搭建 Selenium常用的API使用 定位元素findElement CSS选择语法 id选择器&#xff1a;#id 类选择 .class 标签选择器 标签名 后代选择器 父级选择器 自己选择器 xpath …

TeeChart for .NET 2023.10.19 Crack

TeeChart.NET 的 TeeChart 图表控件提供了一个出色的通用组件套件&#xff0c;可满足无数的图表需求&#xff0c;也针对重要的垂直领域&#xff0c;例如金融、科学和统计领域。 数据可视化 数十种完全可定制的交互式图表类型、地图和仪表指示器&#xff0c;以及完整的功能集&am…

debian、ubuntu打包deb包工具,图形界面deb打包工具mkdeb

debian、ubuntu打包deb包工具&#xff0c;图形界面deb打包工具mkdeb&#xff0c;目前版本1.0 下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1QX6jXNMYRybI9Cx-1N_1xw?pwd8888 md5&#xff1a; b6c6658408226a8d1a92a7cf93834e66 mkdeb_1.0-1_all.deb

听GPT 讲Rust源代码--library/std(2)

File: rust/library/std/src/sys_common/wtf8.rs 在Rust源代码中&#xff0c;rust/library/std/src/sys_common/wtf8.rs这个文件的作用是实现了UTF-8编码和宽字符编码之间的转换&#xff0c;以及提供了一些处理和操作UTF-8编码的工具函数。 下面对这几个结构体进行一一介绍&…

【学习笔记】Git开发流程

Git开发大致流程图&#xff1a; 具体流程&#xff1a; 首先一个从仓库的main分支&#xff0c;然后从main分支中拉一个功能分支feature/xxx&#xff0c;在多人开发这个功能的时候拉去自己的个人分支比如&#xff1a;xxx/xxx 。然后每天开发完个人分支后压缩commit&#xff0c;…

vue2.x封装svg组件并使用

第一步&#xff1a;安装svg-sprite-loader插件 <!-- svg-sprite-loader svg雪碧图 转换工具 --> <!-- <symbol> 元素中的 path 就是绘制图标的路径&#xff0c;这种一大串的东西我们肯定没办法手动的去处理&#xff0c; 那么就需要用到插件 svg-sprite-loader …

护眼灯有效果吗?五款好用热门的护眼台灯推荐

可以肯定的是&#xff0c;护眼灯一般可以达到护眼的效果。看书和写字时&#xff0c;光线应适度&#xff0c;不宜过强或过暗&#xff0c;护眼灯光线较柔和&#xff0c;通常并不刺眼&#xff0c;眼球容易适应&#xff0c;可以防止光线过强或过暗导致的用眼疲劳。如果平时生活中需…

12、Python -- if 分支 的讲解和使用

目录 程序结构顺序结构分支结构分支结构注意点不要忘记冒号 if条件的类型if条件的逻辑错误if表达式pass语句 程序流程 分支结构 分支结构的注意点 if条件的类型 if语句的逻辑错误 if表达式 程序结构 Python同样提供了现代编程语言都支持的三种流程 顺序结构 分支结构 循环结构…

django建站过程(3)定义模型与管理页

定义模型与管理页 定义模型[models.py]迁移模型向管理注册模型[admin.py]注册模型使用Admin.site.register(模型名)修改Django后台管理的名称定义管理列表页面应用名称修改管理列表添加查询功能 django shell交互式shell会话 认证和授权 定义模型[models.py] 模仿博客形式&…

Mysql如何理解Sql语句?MySql分析器

1. 什么是 MySQL 分析器? MySQL 分析器是 MySQL 数据库系统中的一个关键组件&#xff0c;它负责解析 SQL 查询语句&#xff0c;确定如何执行这些查询&#xff0c;并生成查询执行计划。分析器将 SQL 语句转换为内部数据结构&#xff0c;以便 MySQL 可以理解和执行查询请求。 …

全是干货!2023年双十一买什么最划算、双十一值得买的好物推荐

在双十一前选购到好物&#xff0c;打败99.99%的人&#xff01;看了下日历马上就要到一年一度的购物节了&#xff0c;双十一都想好买什么了吗朋友们&#xff1f;双十一购物狂欢即将来临&#xff0c;你是否已经开始准备购买自己心仪的商品&#xff1f;在这个购物狂欢节中&#xf…

【算法小课堂】深入理解前缀和算法

前缀和是指某序列的前n项和&#xff0c;可以把它理解为数学上的数列的前n项和&#xff0c;而差分可以看成前缀和的逆运算。合理的使用前缀和与差分&#xff0c;可以将某些复杂的问题简单化。 我们通过一个例子来理解前缀和算法的优势&#xff1a; 一维前缀和&#xff1a; ww…