Long Animation Frames API 怎么定位卡顿脚本
页面点击、滚动或动画突然卡一下时,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。
Go constant.ToInt 怎么判断编译期数值是否为整数
- 上一篇
- Go constant.ToInt 怎么判断编译期数值是否为整数
- 下一篇
- 特效变音魔术师数据安全怎么看?收集范围、传输加密与删除请求说明
-
- 文章 · 前端 | 3小时前 | html · javascript · css · html Popover API popovertarget 前端弹层
- HTML Popover API 怎么实现轻量弹层
- 444浏览 收藏
-
- 文章 · 前端 | 5小时前 | websocket · javascript · 异步编程 · JavaScript websocket AbortSignal 异步迭代器 Promise.withResolvers EventTarget
- JavaScript Promise.withResolvers 适合哪些事件桥接场景
- 431浏览 收藏
-
- 文章 · 前端 | 7小时前 | javascript · JavaScript Fetch AbortController 取消请求 AbortSignal.any AbortSignal.timeout
- JavaScript AbortSignal.any 怎么组合多个取消来源
- 174浏览 收藏
-
- 文章 · 前端 | 10小时前 |
- JavaScript scheduler.yield 怎么让长任务主动让出主线程
- 255浏览 收藏
-
- 文章 · 前端 | 12小时前 |
- View Transition API 怎么给跨文档导航添加过渡
- 181浏览 收藏
-
- 文章 · 前端 | 14小时前 | css · CSS 自定义属性 container query
- CSS Style Container Query 怎么按自定义属性切换样式
- 440浏览 收藏
-
- 文章 · 前端 | 17小时前 | typescript · 前端工程 · VUE shallowRef 第三方实例 triggerRef markRaw
- Vue shallowRef 管理第三方实例的响应式边界
- 246浏览 收藏
-
- 文章 · 前端 | 18小时前 | React useTransition startTransition 并发渲染
- React useTransition 区分交互更新与后台渲染
- 112浏览 收藏
-
- 文章 · 前端 | 21小时前 | vite 前端缓存 Vite依赖预构建 optimizeDeps node_modules/.vite
- Vite 依赖预构建缓存失效时的排查步骤
- 451浏览 收藏
-
- 文章 · 前端 | 2天前 |
- 表单 aria-describedby 关联错误提示的可访问设计
- 349浏览 收藏
-
- 文章 · 前端 | 2天前 | 前端 · css · 组合选择器 CSS :is specificity :where 级联层
- CSS :is 组合选择器时的 specificity 控制
- 327浏览 收藏
-
- 文章 · 前端 | 2天前 | javascript · DNS耗时 PerformanceResourceTiming TLS耗时
- PerformanceResourceTiming 拆分 DNS 与 TLS 耗时
- 108浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 327次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 385次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 377次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 344次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 170次使用
-
- Go语言对前端领域的入侵WebAssembly运行原理
- 2022-12-31 130浏览
-
- web项目中golang性能监控解析
- 2023-01-08 188浏览
-
- golang调试bug及性能监控方式实践总结
- 2023-05-13 278浏览
-
- Go runtime/metrics 怎么读取 GC 周期:采样间隔、直方图与监控验收
- 2026-08-25 362浏览
-
- Go runtime/metrics.Float64Histogram 如何读取延迟分布:桶边界、计数快照与百分位估算
- 2026-08-30 197浏览

