news 2026/9/19 17:32:28

Pace源码剖析(一):四大进度收集器如何感知页面加载——Ajax、Elements、Document与EventLag

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Pace源码剖析(一):四大进度收集器如何感知页面加载——Ajax、Elements、Document与EventLag

Pace源码剖析(一):四大进度收集器如何感知页面加载——Ajax、Elements、Document与EventLag

【免费下载链接】paceAutomatically add a progress bar to your site.项目地址: https://gitcode.com/gh_mirrors/pa/pace

Pace 是一个能"自动为网站添加进度条"的开源 JavaScript 库,它的核心思路是:同时派出四大进度收集器(Ajax、Elements、Document、EventLag),从网络请求、DOM 元素、文档就绪状态、事件循环延迟四个维度感知页面加载进度,再把结果平滑地渲染成顶部那条优雅的进度条。本文带你逐行走读 pace.js 源码,看看这四个"侦查兵"分别在做什么。

一、先看全景:一个文件里的四大模块 🧭

整个库的逻辑全部集中在 pace.js 中,压缩后仅 4KB 左右。它的运行流程可以概括为一条流水线:

收集器(Sources)→ 缩放器(Scaler)→ 进度条(Bar)

  • 四个收集器各自维护一个progress(0~100)数值;
  • Scaler按帧读取各收集器的进度,做"追赶 + 缓动"平滑处理;
  • Bar负责创建 DOM 节点,用translate3d移动进度条位置。

四大收集器在初始化时被统一注册,源码位置在 pace.js#L869-L894:

SOURCE_KEYS = { ajax: AjaxMonitor, elements: ElementMonitor, document: DocumentMonitor, eventLag: EventLagMonitor };

init()会遍历['ajax', 'elements', 'document', 'eventLag'],只要配置中对应项不为false,就实例化并加入sources数组。也就是说,每个收集器都可以单独关闭,这正是后文配置指南的基础。

进度条本体由Bar类负责,见 pace.js#L262-L342。它的render()方法把进度换算成translate3d(x%, 0, 0)的 GPU 加速位移,同时把百分比写入data-progress-text属性——这就是很多主题 CSS 能显示"37%"文字的原因。

二、Ajax 收集器:监听页面上的每一次请求 📡

Ajax 收集器(AjaxMonitor,pace.js#L581-L613)的职责是:监控页面上所有的异步请求,每个进行中的请求都算"未完成的工作"。它由三个部分组成。

2.1 劫持请求构造函数:怎么"偷听"到请求?

关键在RequestIntercept类(pace.js#L447-L514)。它在库加载时保存了原始的window.XMLHttpRequestwindow.XDomainRequestwindow.WebSocket,然后把它们替换成包装过的构造函数

  1. 每次new XMLHttpRequest()时,包装器创建真实请求对象,并给它打补丁——重写req.open方法;
  2. 一旦业务代码调用open(method, url),包装器就触发request事件,把请求信息交给AjaxMonitor.watch()

这是典型的"猴子补丁"(Monkey Patching)手法:不改业务代码,只改浏览器 API 本身。WebSocket 同样被包装(受ajax.trackWebSockets选项控制),所以实时推送类页面也能被感知到。

哪些请求会被跟踪,由shouldTrack()(pace.js#L429-L445)决定:默认只跟踪GET请求和 socket;另外提供了Pace.ignore()Pace.track()(pace.js#L411-L427)两个函数,用栈机制(ignoreStack)临时标记"这段代码里的请求要忽略/强制跟踪",避免预缓存请求触发进度条。

2.2 单请求进度:XHRRequestTracker

每个被跟踪的请求都会生成一个XHRRequestTracker(pace.js#L615-L654),它内部有个巧妙的两档策略:

场景进度计算方式
浏览器支持ProgressEvent且能算出总大小(lengthComputable100 × 已加载字节 / 总字节数,精确计算
拿不到总大小progress = progress + (100 - progress) / 2,即永远向 100 逼近一半,呈现"越接近终点越慢"的观感
老浏览器(只有readyStatereadyState === 3时直接记为 50%

请求的loadaborttimeouterror任一事件触发后,进度置 100 并通知AjaxMonitor移除该 tracker。

2.3 何时重新开始?两条重启规则 🔄

Ajax 收集器还承担了"进度条重启"的触发职责:

  • pushState 重启:库加载时重写history.pushStatereplaceState(pace.js#L847-L867),单页应用每次路由切换都会调用Pace.restart(),进度条从头再来;
  • 慢请求重启:监听request事件后,延迟restartOnRequestAfter(默认 500ms)检查请求是否仍在飞行中(readyState介于 0 和 4 之间),若是则重启进度条并接管该请求(pace.js#L543-L579)。

此外ajax.ignoreURLs(pace.js#L525-L541)支持字符串或正则黑名单,埋点、统计类请求可以一键屏蔽。

三、Elements 收集器:盯着 DOM 等元素出现 👀

ElementMonitor(pace.js#L675-L701)的思路非常朴素:"关键元素渲染出来了,页面就算完成了"。它为每个选择器创建一个ElementTracker(pace.js#L703-L730),后者用递归setTimeout轮询:

if (document.querySelector(this.selector)) { return this.done(); // 出现了 → 完成 } setTimeout(() => this.check(), options.elements.checkInterval); // 没出现 → 100ms 后再问一次
  • 轮询间隔由elements.checkInterval控制,默认100ms
  • 默认选择器是['body'],几乎总是立刻完成,所以默认配置下它基本不拖后腿;
  • 真正的用法是给选择器写**"成功或错误态"的逗号组合**,例如.timeline, .timeline-error——两者任一出现即算完成,避免出错时元素永远不出现导致进度条卡死(详见 README.md 的 Elements 一节)。

四、Document 收集器:读取文档就绪状态 📄

DocumentMonitor(pace.js#L732-L754)是四个收集器中最短的一个,只有 20 来行。它把document.readyState的三个状态直接映射成进度:

states = { loading: 0, interactive: 50, complete: 100 };

构造时先按当前状态取初始值(已晚加载则直接 100),随后接管document.onreadystatechange事件(保留了用户原有的回调并链式调用)。它代表的是文档骨架层面的进度:脚本执行、DOM 解析完毕走到 50%,资源全部加载完毕(complete)到达 100%。

五、EventLag 收集器:用"卡顿"反推进度 ⏱️

这是四个收集器里最有意思的一个。EventLagMonitor(pace.js#L756-L785)基于一个朴素假设:

页面在加载时,浏览器忙着解析 HTML、执行脚本,事件循环会被"拖延";页面越空闲,定时任务越准点。

它每50ms设一个定时器,每次触发时测量"实际延迟 - 预期 50ms"得到本次 lag,保留最近sampleCount个样本(默认 3)求平均绝对值avg,然后:

  • 进度公式progress = 100 × 3 / (avg + 3)——平均延迟越小,进度越接近 100;完全空闲时恰好 100;
  • 完成判定:累计minSamples次采样(默认 10 次)且平均 lag 小于lagThreshold(默认 3ms),判定页面已空闲,进度锁定 100 并clearInterval停止计时。

也就是说,EventLag 收集器本质上是在做CPU 空闲度采样,它兜住了那些没有任何 XHR、也没有关键元素、但一直在跑脚本的场景。

六、最后一步:Scaler 把四个进度"揉"成丝滑动画 ✨

四个收集器的原始值都带毛刺(有的 0 直接跳 100),直接上屏会很难看。Scaler类(pace.js#L787-L831)用requestAnimationFrame逐帧(约 33ms 一帧,兼容实现见 pace.js#L82-L98)做两层加工:

  1. catchup(追赶):真实值与显示值的差距除以catchupTime(默认 100ms),以固定速率追上去,保证"真完成"后进度条不会立刻弹满;
  2. rate(变速):按最近一次变化的斜率外推,并用easeFactor(默认 1.25)做缓动衰减——进度越高爬得越慢,形成"头快尾慢"的自然节奏。

Pace.go()(pace.js#L916-L956)是总调度:每帧对每个收集器的每个元素tick()一次,取平均后交给全局uniScaler,再更新Bar。当所有 Scaler 都done或进度满 100 时触发done事件,并按minTime(默认 250ms)/ghostTime(默认 100ms)取最大值延迟淡出——这就是进度条"跑满了还会多留一会儿"的原因。

七、速查表:四大收集器对比与默认配置

收集器感知对象完成信号关键默认值
AjaxXHR / XDR / WebSocket 请求所有请求结束trackMethods: ['GET']restartOnRequestAfter: 500ms
Elements指定选择器的 DOM 元素每个选择器都匹配到checkInterval: 100msselectors: ['body']
Documentdocument.readyStatecompleteloading=0 / interactive=50 / complete=100
EventLag事件循环延迟连续空闲采样达标采样间隔 50ms、minSamples: 10lagThreshold: 3ms

所有默认值集中定义在 pace.js#L14-L40 的defaultOptions。想关闭某个收集器,只需在window.paceOptions(或<script>标签的data-pace-options)里将其设为false

paceOptions = { ajax: false, eventLag: false };

八、小结:一文读懂 Pace 的感知体系 🎯

回顾整条链路:RequestIntercept劫持网络 API 喂给AjaxMonitorElementTracker定时轮询喂给ElementMonitorreadyState映射喂给DocumentMonitor,定时器漂移反推喂给EventLagMonitor——四路信号汇入Scaler平滑、再由Bar渲染。这套"多信号融合 + 时间平滑"的架构,就是 Pace 无需任何手动埋点就能自动感知页面加载的全部秘密。

相关资源:

  • 完整配置说明与主题列表:README.md
  • 各主题样式:themes/ 下 black / blue / green 等 10 套配色 × 15 种主题,以及 pace-theme-default.css
  • 压缩版入口:pace.min.js

下一篇预告:Pace 源码剖析(二)将深入Scaler的缓动数学、Pace.on/off/once事件系统与extraSources自定义收集器扩展。

【免费下载链接】paceAutomatically add a progress bar to your site.项目地址: https://gitcode.com/gh_mirrors/pa/pace

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 17:30:19

济南樱花燃气灶故障维修电话|燃气灶漏气检测|欧米到家咨询电话

燃气灶是济南家庭日常烹饪中使用频率很高的设备&#xff0c;涉及点火、燃烧、熄火保护、阀体和燃气连接等多个安全环节。遇到燃气灶打不着火、有火花却点不燃、一松手就熄火、火焰发黄发红、火力变小、锅底熏黑、旋钮拧不动、关火后持续打火&#xff0c;或闻到燃气异味等情况时…

作者头像 李华
网站建设 2026/9/19 17:28:46

uni-app 多客户多平台自动发布:HBuilderX 工程改造 CLI 实战

有一段时间&#xff0c;我的工作状态基本是&#xff1a;打开 HBuilderX&#xff0c;同时打开六七个 uni-app&#xff08;Vue2&#xff09;项目&#xff0c;挨个点“发行”&#xff0c;选微信小程序&#xff0c;等编译完再切到 H5&#xff0c;等到新客户上线那几天&#xff0c;一…

作者头像 李华
网站建设 2026/9/19 17:28:19

遥感旋转框转YOLO格式实战:DOTA数据集坐标转换与训练避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 17:24:43

卡方分布的前世今生:从测量误差到假设检验的基石

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 17:24:25

分布式能源集群的联合推理与小样本学习协同调度

简介&#xff1a;本资源是一份面向能源智能化领域研发人员、电力系统调度工程师及AI能源交叉方向研究者的深度技术方案&#xff0c;聚焦分布式能源集群在多源异构、小样本、强实时约束下的协同优化调度难题。文档系统提出基于DeepSeek大模型的联合推理与小样本学习融合技术路径…

作者头像 李华
网站建设 2026/9/19 17:24:05

2026年PyCharm安装教程:从下载到配置Python环境完整指南

1. 为什么2026年还要认真装一次PyCharm先把结论放前面&#xff1a;PyCharm 到了 2026 年这个版本&#xff0c;安装这件事本身已经比五年前简单太多了&#xff0c;但“装完能跑、跑得顺、跑得久”这三件事&#xff0c;依然是新手最容易翻车的地方。我见过太多人卡在解释器选错、…

作者头像 李华