惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
博客园 - 聂微东
博客园 - 叶小钗
量子位
Google DeepMind News
Google DeepMind News
Cyberwarzone
Cyberwarzone
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Privacy & Cybersecurity Law Blog
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
雷峰网
雷峰网
C
Cybersecurity and Infrastructure Security Agency CISA
Schneier on Security
Schneier on Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
美团技术团队
T
Tenable Blog
T
Threatpost
S
Security Affairs
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
C
Cisco Blogs
The Last Watchdog
The Last Watchdog
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
A
Arctic Wolf
Attack and Defense Labs
Attack and Defense Labs
AWS News Blog
AWS News Blog
AI
AI
W
WeLiveSecurity
H
Help Net Security
G
Google Developers Blog
PCI Perspectives
PCI Perspectives
博客园_首页
S
Securelist
B
Blog RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Forbes - Security
Forbes - Security
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
Lohrmann on Cybersecurity
云风的 BLOG
云风的 BLOG
Martin Fowler
Martin Fowler
C
CERT Recently Published Vulnerability Notes
H
Hacker News: Front Page
The GitHub Blog
The GitHub Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Spread Privacy
Spread Privacy
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Threat Research - Cisco Blogs
博客园 - 司徒正美
Google Online Security Blog
Google Online Security Blog
T
The Blog of Author Tim Ferriss

蚊子的前端博客

微说 | 有的人觉得只要不考XX,一定能考好!-蚊子的前端博客 微说 | 春天来了-蚊子的前端博客 明天和意外不知道哪个先来-蚊子的前端博客 微说 | 既然错过了路口,就及时止损,重新规划路线上路-蚊子的前端博客 微说 | 2025年的出生人口数是792万-蚊子的前端博客 微说 | 工作年终总结,让写对接的接口的数量?-蚊子的前端博客 微说 | 温家宝:没有政治体制改革的成功 经济体制改革不可能进行到底-蚊子的前端博客 微说 | 违法犯罪了该不该被禁言?-蚊子的前端博客 微说 | 不明白为什么在推荐去俄罗斯旅游-蚊子的前端博客 微说 | 易中天论骗子-蚊子的前端博客 微说 | 一场大风吹散了秋天-蚊子的前端博客 微说 | 既要、又要、还要、更要!-蚊子的前端博客 又是一年的国庆雨季-蚊子的前端博客 微说 | 预估下2025年的出生人口数据-蚊子的前端博客 微说 | 可惜了我那些小时候的书本-蚊子的前端博客 微说 | 牛马有的是,驴不够了!-蚊子的前端博客 微说 | 能否有一条非户口也能高考的路-蚊子的前端博客 微说 | 小聊中医-蚊子的前端博客 微说 | 将要制作的一款新产品-蚊子的前端博客 微说 | 做人要有信-蚊子的前端博客 微说 | 好一个正义联盟-蚊子的前端博客 微说 | 都是见过吃过的主儿-蚊子的前端博客 微说 | 追求8小时工作制有错吗?-蚊子的前端博客 微说 | 如果尖锐的批评完全消失-蚊子的前端博客 微说 | 很好!-蚊子的前端博客 微说 | 封禁用户可以,但要告知具体原因-蚊子的前端博客 微说 | 面朝大海,春暖花开-蚊子的前端博客 微说 | 程序员的悲哀是什么?-蚊子的前端博客 微说 | 2025年出生人口的预测-蚊子的前端博客 前端在 LiveKit 中如何获取所有的参与者-蚊子的前端博客 在 Electron 中使用 LiveKit 实现屏幕共享-蚊子的前端博客 在 Electron 应用中,如何获取 Mac 的摄像头和麦克风权限-蚊子的前端博客 微说 | 不要总让台湾艺人表态-蚊子的前端博客 微说 | 他们总说要更开放-蚊子的前端博客 自己封装 uuid 踩的一个小坑-蚊子的前端博客 CKEditor5 光标在粘贴的图片下一行-蚊子的前端博客 软考出成绩了,没过-蚊子的前端博客 微说 | 也许有一天我们这个社会会进步-蚊子的前端博客 好久没有更新文章了-蚊子的前端博客 微说 | 中共中央关于进一步全面深化改革,推进中国式现代化的决定-蚊子的前端博客 微说 | 关于惩罚性赔偿-蚊子的前端博客 微说 | 生孩子是谁的事儿!-蚊子的前端博客 微说 | 给群众一些选择电视剧的空间-蚊子的前端博客 微说 | 活着-蚊子的前端博客 微说 | 借用睡前消息里面的一段话-蚊子的前端博客 微说 | 曾经梦寐以求的,现在却没了追求-蚊子的前端博客 微说 | 我小时候的那些书本-蚊子的前端博客 给博客添加了一个微说的功能-蚊子的前端博客 今年的双 11 很平静-蚊子的前端博客 微说 | 贪就是贪-蚊子的前端博客 微说 | 总有人说穿越小说为什么不发展工业和科技!-蚊子的前端博客 微说 | 花了很多时间和精力,-蚊子的前端博客 2025 年终于是增加了 2 天的假期-蚊子的前端博客 微说 | 这是我的第一条微说,-蚊子的前端博客 如何手写 Array 的 forEach 方法-蚊子的前端博客 看到聚美优品的没落,真是唏嘘-蚊子的前端博客 如何手写 Array 的 map 方法-蚊子的前端博客 秋意正浓时-蚊子的前端博客 收藏几个常用但访问比较慢的组件的官网网址-蚊子的前端博客 从 Axios 源码分析如何支持 fetch 方法的-蚊子的前端博客 1024,给博客评论区所有的头像戴上一顶可爱的帽子-蚊子的前端博客 使用 GitHub 搭建了一个静态博客-蚊子的前端博客 在 JavaScript 如何判断变量是否为空-蚊子的前端博客 我为什么还在坚持我的个人博客-蚊子的前端博客 不要再手动拼接 URL 参数,请使用 URLSearchParams-蚊子的前端博客 使用 NodeJs 向百度资源推送链接-蚊子的前端博客 工具链极度内卷,留给开发者的性能优化手段已经不多了-蚊子的前端博客 在 React 中显示多个标签,超出省略并可以 hover 显示更多-蚊子的前端博客 蚊子的前端博客:探索与分享前端技术的奇妙之旅-蚊子的前端博客 互联网程序员前景真的一片黑暗吗?-蚊子的前端博客 Deno2.0 正式发布,向 Node.js 兼容-蚊子的前端博客 React 请求数据别再使用 useEffect 和 useState,试试 SWR 吧!-蚊子的前端博客 React 的 useEffect 的一些使用场景和技巧-蚊子的前端博客 前端趣闻之 JavaScript 语言的诞生和发展-蚊子的前端博客 如何合并同一接口的相同参数的请求-蚊子的前端博客 百度搜索中关于蚊子前端博客的奇怪的检索数据-蚊子的前端博客 使用 React 实现 todo list 的 curd 操作-蚊子的前端博客 React 模板中为什么可以用逻辑与运算符-蚊子的前端博客 评论回复在一张表里的评论系统如何进行分页-蚊子的前端博客 React 组件多次调用时如何区分不同的 div 容器-蚊子的前端博客 安全赋值运算符,再也不用写 try-catch 了-蚊子的前端博客 使用 nextjs 重构我的个人博客-蚊子的前端博客 基于 gitlab 的 webhook 向飞书发通知-蚊子的前端博客 数字转换为更高量级单位的工具方法-蚊子的前端博客 如何避免旧请求的数据覆盖掉最新请求-蚊子的前端博客 JavaScript 中 toString 的冷知识-蚊子的前端博客 React 中 useState 和 useRef 与全局变量的区别-蚊子的前端博客 从面试官的角度分析下简历中存在的问题-蚊子的前端博客 使用 React 实现 6 个输入框的短信验证码功能-蚊子的前端博客 反驳那些要实时刷新页面的前端部署方案-蚊子的前端博客 JSON stringify 的一些不常见使用-蚊子的前端博客 JavaScript 中数组 Array 的常见操作-蚊子的前端博客 前端如何提升用户的交互体验-蚊子的前端博客 没有日的日期在iOS中报 Invalid Date 的探究-蚊子的前端博客 飞书很好,但还是没抗住-蚊子的前端博客 给 Antd 的 DatePicker 组件实现带有至今的功能-蚊子的前端博客 antd 的 InputNumber 输入框添加左右的加减按钮-蚊子的前端博客 给想进国央企的同学介绍一款招聘软件「国聘」-蚊子的前端博客 基于 React 和 antd 实现的图片裁剪压缩功能-蚊子的前端博客 uniapp 中 checkbox 中的 checked 不生效的方案-蚊子的前端博客
页面中长列表滚动的优化-蚊子的前端博客
author · 2019-04-23 · via 蚊子的前端博客

当我们有 10 万条或者更多的数据需要展示在页面中,我们应该怎样处理呢?

这里主要的方式有两种:

  1. 懒加载:即监听 scroll 事件或使用IntersecionObserver监听;
  2. 可视区域的渲染:仅在可视区域展示数据,为保证滚动条的完整性,非可视区域使用占位元素的高度后者容器的位移来撑开。

1. 懒加载 #

懒加载的方式有两种:监听 scroll 事件或使用IntersecionObserver监听某个元素。

1.1 scroll 事件 #

监听滚动事件应该是我们使用的最多的事件了,当滚动条滑动到页面最底部或者将要滑动到最底部的时候,去加载下一页的数据。同时图片懒加载或者其他组件的懒加载也可以依赖于滚动事件!

为了优化滚动事件,我们也会给滚动事件添加上防抖和节流

这是一个很常见的 demo,无限加载数据系列,当滚动条滑动到底部时,加载下一页的数据:监听滚动事件懒加载数据

请分别用微信和 QQ 扫下这个二维码体验一下,体验时,请点击“向左”的按钮关闭代码的部分:

监听滚动事件懒加载数据-蚊子博客

其实我们发现会有一个很有意思的体验:在 android 中的微信和 QQ 中基本可以无限的滑动,在 iOS 的微信中也可以,但是在 iOS 的 QQ 中,当我们一直滑动时,需要滑动到底部,然后才能加载新的数据,才能继续滑动!

这种原因是 iOS 不同的 webview 造成的。在 iOS 中,UIWebView 是在 iOS2 中出现的,性能更好的 WKWebView 跟随 iOS8 一起出现。从性能方面来说,WKWebView 会比 UIWebView 高很多,可以算是一次飞跃。它采用了跨进程的方案,用 Nitro JS 解析器,高达 60fps 的刷新率。同时,提供了很好的 H5 页面支持,类比 UIWebView 还多提供了一个加载进度的属性。同时,在 UIWebView 中,无论是页面的 body 滚动,还是 div 级别的局部滚动,都不会实时触发,而是在滚动结束后才会触发!

可以分别体验下面的两个二维码,左侧的为 body 级别的滚动,右侧为 div 的局部滚动:

body级别的滚动-蚊子博客 div的局部滚动-蚊子博客

总结下滚动事件在不同的 webview 中的表现情况:

机型 body 滚动 局部滚动
iOS WKWebView 实时触发 实时触发
iOS UIWebView 无法实时触发 无法实时触发
Android 实时触发 实时触发

那么该如何优化滚动的性能呢?

首先就是防抖和节流。在 Chrome 浏览器中使用performance.now()测试滚动每次触发的间隔,可以看到,频率大概在 16ms 左右,但很多时候我们并不需要这么快地触发我们的监听函数,而且,比如图片懒加载等场景,也要及时的更新一次,这时候就用到了防抖和节流!

再有就是使用requestAnimationFramerequestIdleCallback代替定时器。若使用定时器进行间歇性触发监听函数,会出现掉帧的情况,我们也知道setTimeout(fn, 100),不一定是正好在 100ms 后触发 fn 函数,而是等浏览器空闲之后才调用 fn 函数,当多次积累后就会产生掉帧的,那么使用requestAnimationFramerequestIdleCallback,则浏览器会根据自己渲染的频率适时地执行回调。在追求高性能的渲染效果时,可以考虑用 requestIdleCallback()和 requestAnimationFrame()代替定时器。前者适合流畅的动画效果场景,后者适用于分离一些优先级低的操作逻辑,使用时需要考虑清楚

1.2 IntersecionObserver #

使用 IntersecionObserver 也可以实现无限滚动,比如在底部监听一个透明的元素,当该元素可见时就加载新的资源!

demo: IntersecionObserver 实现无限滚动

不过也应当看到的是 IntersecionObserver 的支持程度还不太好,如果要使用的话,还是需要 polyfill 方案!

IntersecionObserver的浏览器支持程度

而且 IntersecionObserver 在图片懒加载和组件懒加载的过程中,非常有用,我们后续会进行了解!

2. 模拟滚动 #

模拟滚动最代表的例子就是iScroll,监听手势的 touchmove 事件,然后使用 CSS3 中的 transform 产生位移。

不过在模拟滚动的过程中,若图片比较多时,可以感觉到滚动时有明显的卡顿感。查看这个样例:https://www.xiabingbao.com/demos/20190423/iscroll-scroll.html

这是利用 IScroll 中的scroll事件,无限地向页面中添加元素。不过当页面的数据是无限加载时,应当使用iscroll-infinite这个类库更好,这部分留到后面讲解!

3. 可视区域数据的渲染 #

上面的几种方法里,我们都是无限地直接往页面中添加元素,滑动的页码越大,页面中的 DOM 元素也就越多。但实际上,这是没有必要的,滚动到可视区域外的元素,用户是看不到的,也没不要保留在页面中,可以用一个占位元素或者 transform 撑开上面所有不可见元素的高度,让滚动条能够正常的上下滑动即可!

撑开上半部分不可见区域的高度,有两种方法,一种是在容器内的最顶部设置一个占位元素,这个占位元素的高度就是消失的所有 DOM 的高度;再一个就是给容器或者占位元素一个 transform 的位移!这里我们使用给占位元素一个高度撑起顶部的滚动区域。

3.1 固定高度的 item #

若每个 item 的 height/margin/padding 等数据都是固定写死的,这种情况比较容易实现!获取一次数据后,不用每次都重新计算。

每次滚动时,都要计算应当渲染列表的哪部分!start表示列表开始的位置,fixedScrollTop表示顶部占位元素的距离

// 滚动处理函数
function handleScroller() {
  let lastStart = 0; // 上次开始的位置
  const item = document.querySelector('.container .item');
  const itemStyle = getComputedStyle(item);
  const itemHeight = item.offsetHeight + parseInt(itemStyle['marginTop']) + parseInt(itemStyle['marginBottom']);

  return function () {
    const currentScrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);
    const fixedScrollTop = currentScrollTop - (currentScrollTop % itemHeight);
    let start = Math.floor(currentScrollTop / itemHeight); // 可视区域开始渲染的位置

    if (lastStart !== start) {
      lastStart = start;
      createDom(start, count, fixedScrollTop);
    }
  };
}

设置顶部滚动的高度:

function createDom(start, count, height) {
  const container = document.querySelector('.container');
  // container.style.transform = `translateY(${height}px)`; // 给容易一个位移

  let div = document.createDocumentFragment();

  // 创建占位元素
  if (height) {
    let p = document.createElement('p');
    p.style.height = height + 'px';
    div.appendChild(p);
  }

  for (let i = start, len = start + count; i < len; i++) {
    let item = document.createElement('div');
    item.className = 'item';
    item.innerHTML = i;
    div.appendChild(item);
  }

  // 为了方便处理,我们这里采用了更新container中的全部元素
  // 你也可以尝试只增加/删除首位的元素,中间元素不变
  container.innerHTML = '';
  container.appendChild(div);
}

可以点击链接查看样例:可视区域渲染之 DOM 元素增减,审查元素可以看得更清晰,无论我们怎么滚动,.container中永远只有那么几个元素!

3.2 每个 item 的高度都不一定 #

待定,有个问题一直搞不定

3.3 DOM 的复用 #

重点来了,在上面的章节里,我们进行了进一步的优化,只渲染可视区域内的数据。但是还是存在一个重要的问题:频繁的改动 DOM,这样会频繁的引起页面的重绘。这里我们的思想是对页面中 DOM 元素进行复用,如下图中所示,从上面滑出的元素,可以直接定位下面再重新装填元素,反之亦然!

DOM元素的复用-蚊子博客-蚊子博客

可以点击链接查看 demo,在审查元素中我们可以看到,在滚动的过程中,DOM 并没有被删掉,而是改变了 transform,放到了最下面:可视区域渲染之 DOM 元素复用

// 更新页面中DOM元素的位置
function updateDom(start, count, itemHeight, height) {
  document.querySelector('.container .content').style.transform = 'translateY(' + height + 'px)';
  for (var i = start, len = start + count; i < len; i++) {
    var index = i % count;
    var cssIndex = (i - start) % len;
    document.querySelector('.item' + index).innerHTML = i;
    document.querySelector('.item' + index).style.transform = 'translateY(' + itemHeight * cssIndex + 'px)';
  }
}

这里我们还是监听了元素的 scroll 事件,我们在上面一笔带过的iScroll-infinite类库,是使用了模拟滚动+DOM 复用。点击链接查看 demo:iScroll-infinite 实现的无限加载

4. 总结 #

最有效的方案应当是模拟滚动+DOM 复用,既解决了滚动不能实时生效的问题,又能解决页面中 DOM 元素过多,造成滚动中卡顿的问题。

总是想着要讲的有很多,不过最后好像哪方面都欠缺了一点,后面有机会再针对其中的某一个点再深入进行探讨!例如使用IntersecionObserver实现 Vue 组件的懒加载,如何自己实现一个简单的模拟滚动等。

参考: