Long Animation Frames API 怎么定位卡顿脚本
来源:17golang原创
时间:2026-10-04 17:44:48 463浏览 收藏
页面点击、滚动或动画突然卡一下时,Long Animation Frames API(LoAF)可以把超过 50ms 的长帧记录到性能时间线,并在 scripts 中给出贡献脚本的地址、入口函数、字符位置和耗时。定位时先按 blockingDuration 找最影响响应的长帧,再按 sourceURL + sourceCharPosition 聚合脚本;这会得到值得复查的候选,不应直接把耗时最长的一项等同于唯一根因。
- 先用
PerformanceObserver.supportedEntryTypes检测long-animation-frame,不要假定所有浏览器都支持。 - 帧级先看
duration、blockingDuration和firstUIEventTimestamp,脚本级再看duration、invoker与source*字段。 sourceFunctionName通常是脚本入口点,不是完整调用栈中的最慢子函数;跨域 iframe、Worker 和扩展代码也可能缺少归因。
先确认浏览器能不能提供 LoAF 数据
LoAF 仍不是所有浏览器都具备的通用能力。初始化监控前检查支持类型;不支持时保留基础交互指标、Long Tasks 或现场性能分析作为降级路径。这样不会因为一段实验性监控代码影响主业务。
const LOAF_TYPE = "long-animation-frame";
// 运行时检测支持范围,避免在不支持的浏览器中直接注册。
const supportsLoAF =
"PerformanceObserver" in window &&
PerformanceObserver.supportedEntryTypes.includes(LOAF_TYPE);
if (!supportsLoAF) {
// 这里只记录能力缺失;业务逻辑必须继续正常运行。
console.info("Long Animation Frames API is unavailable");
}
规范把长动画帧定义为持续时间超过 50ms 的帧。这个门槛由 API 决定,但你的上报门槛可以更高,例如先只保留 120ms 或 150ms 的严重样本,降低数据量。性能时间线的 LoAF 缓冲区容量有限,MDN 提醒最多保留 200 条,因此持续监控应优先使用 PerformanceObserver,而不是等页面结束时再一次性读取。
先采集长帧,再判断卡顿落在哪一层
遇到“点击后过一会儿才响应”的现场,先不要急着只看脚本文件。duration 表示整段长帧持续时间,blockingDuration 更接近主线程无法及时响应高优先级任务的阻塞量;firstUIEventTimestamp 大于 0 时,说明帧内处理过 UI 事件。renderStart 与 styleAndLayoutStart 则帮助判断时间是否大量落在渲染、样式和布局区域。

const SERIOUS_FRAME_MS = 120;
const recentLoAFs = [];
function observeLongAnimationFrames() {
if (!PerformanceObserver.supportedEntryTypes.includes("long-animation-frame")) {
return null;
}
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 先保留严重长帧,避免把所有 50ms 样本都送入后续分析。
if (entry.duration 20) recentLoAFs.shift();
}
});
// buffered 同时接收缓冲区内既有条目和后续新条目。
observer.observe({ type: "long-animation-frame", buffered: true });
return observer;
}
这段代码只保存诊断需要的字段,没有上报完整 DOM、用户输入或页面内容。生产采集还应设置抽样率、总量上限和数据保留周期,避免性能监控自己变成额外负担。
从 scripts 提取最值得复查的脚本
entry.scripts 中的每一项是 PerformanceScriptTiming。定位脚本身份时组合使用 sourceURL、sourceFunctionName 和 sourceCharPosition;判断调用来源时看 invoker 与 invokerType;判断成本时看 duration、forcedStyleAndLayoutDuration 和 pauseDuration。

function normalizeScriptURL(rawURL) {
try {
// 去掉查询参数和片段,避免聚合键被版本参数拆散,也减少敏感信息进入日志。
const url = new URL(rawURL, location.href);
return `${url.origin}${url.pathname}`;
} catch {
return "inline-or-unknown";
}
}
function rankScriptContributors(frames) {
const totals = new Map();
for (const frame of frames) {
for (const script of frame.scripts) {
const sourceURL = normalizeScriptURL(script.sourceURL);
const key = [sourceURL, script.sourceCharPosition, script.sourceFunctionName].join("|");
const current = totals.get(key) ?? {
sourceURL,
sourceFunctionName: script.sourceFunctionName || "anonymous-entry",
sourceCharPosition: script.sourceCharPosition,
invoker: script.invoker,
count: 0,
totalDuration: 0,
totalForcedLayout: 0,
};
current.count += 1;
current.totalDuration += script.duration;
current.totalForcedLayout += script.forcedStyleAndLayoutDuration;
totals.set(key, current);
}
}
// 总执行时间优先,次数作为次级信号,找出高频且高成本的入口。
return [...totals.values()].sort(
(a, b) => b.totalDuration - a.totalDuration || b.count - a.count,
);
}
// 调试时查看聚合结果;正式上报只发送必要字段和受控数量。
console.table(rankScriptContributors(recentLoAFs).slice(0, 10));
字符位置比只有文件名更有区分度,因为同一 bundle 可能包含多个入口。若部署时有 source map,可以在后端或调试工具中把 sourceCharPosition 映射回源码;不要在浏览器里假设压缩后的函数名长期稳定。
用字段组合判断 JavaScript、布局还是渲染
| 证据组合 | 优先复查方向 | 常见动作 |
|---|---|---|
脚本 duration 高,强制布局低 | 事件回调、循环、序列化、同步计算 | 拆分长任务、减少工作量、延后非关键逻辑 |
forcedStyleAndLayoutDuration 高 | 脚本触发的同步样式和布局 | 合并 DOM 读写、减少布局抖动 |
脚本总耗时不高,但帧 duration 高 | 渲染、样式、布局或缺失归因 | 比较 renderStart 与 styleAndLayoutStart,继续做现场剖析 |
pauseDuration 高 | alert、同步请求等暂停型操作 | 移除同步阻塞 API |
firstUIEventTimestamp 大于 0 | 与用户交互重叠的长帧 | 优先关联 INP 样本和交互入口 |
blockingDuration 不是简单等于 duration - 50。它会综合帧内超过 50ms 的长任务以及相关渲染成本,更适合用来排序“主线程有多长时间无法及时响应”。分析时保留 duration 与 blockingDuration 两个维度,不要只看一个数字。
归因为空或函数名不准时怎么继续
LoAF 的脚本归因有明确边界。MDN 说明,sourceFunctionName 指向脚本入口点,而不是完整调用栈里的最慢子函数;跨域 iframe、Web Worker、Service Worker 和扩展代码即使影响帧时长,也可能没有脚本归因。scripts 为空并不代表“没有 JavaScript 问题”,只代表当前条目没有可公开的脚本信息。
遇到缺口时按下面顺序复查:
- 确认当前样本来自支持
long-animation-frame的浏览器。 - 检查卡顿是否主要发生在跨域 iframe、Worker 或第三方环境。
- 用
renderStart与styleAndLayoutStart判断是否偏向渲染成本。 - 把脚本入口与真实交互场景关联,再进入本地性能分析寻找入口内部的慢函数。
- 不支持 LoAF 时,用 Long Tasks、Event Timing 或开发者工具保留降级诊断。
修复后用相同维度反向验证
优化前后都使用相同浏览器范围、相同抽样规则和相同聚合键。验收时不要只看某一次长帧消失,而要观察高位样本中目标脚本的出现次数、累计 duration、累计强制布局时间和帧级 blockingDuration 是否一起下降。若脚本耗时下降但长帧仍在,说明瓶颈可能转移到了渲染或另一个入口。
- 已运行时检测,不支持的浏览器不会报错。
- Observer 持续消费条目,并限制内存中的样本数量。
- 脚本聚合键包含地址与字符位置,不只按文件名统计。
- 上报前去掉查询参数,避免泄露令牌、用户标识或实验参数。
- 入口函数只作为线索,最终根因仍通过源码与现场剖析确认。
- 优化后复测使用相同字段和门槛,结果才可比较。
相关问题
LoAF 和 Long Tasks API 有什么区别?
Long Tasks 关注单个长任务;LoAF 以动画帧为单位,还提供脚本归因和渲染时间点,更贴近用户看到的卡顿。多个不足 50ms 的任务也可能共同组成一个长帧。
为什么 scripts 里只有入口函数,没有最慢子函数?
完整调用栈的采集成本较高,API 报告的是脚本入口点。需要借助 source map 和现场性能分析继续深入。
只按 duration 最大值排序够吗?
不够。还要看 blockingDuration、出现次数、是否与 UI 事件重叠,以及强制布局时间,避免把偶发长帧误判成长期热点。
生产环境应该上报完整 sourceURL 吗?
通常不应直接上报带查询参数和片段的完整地址。先归一化到 origin 与 pathname,并限制字段、数量、采样率和保留时间。
参考资料:https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Long_animation_frame_timing;https://w3c.github.io/long-animation-frames/;https://web.dev/articles/find-slow-interactions-in-the-field。
-
130 收藏
-
188 收藏
-
278 收藏
-
362 收藏
-
197 收藏
-
444 收藏
-
文章 · 前端 | 5小时前 | websocket · javascript · 异步编程 · JavaScript websocket AbortSignal 异步迭代器 Promise.withResolvers EventTarget431 收藏
-
文章 · 前端 | 7小时前 | javascript · JavaScript Fetch AbortController 取消请求 AbortSignal.any AbortSignal.timeout174 收藏
-
255 收藏
-
181 收藏
-
440 收藏
-
246 收藏
-
112 收藏
-
451 收藏
-
349 收藏
-
327 收藏
-
108 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习