浏览器在“一帧”(frame)中所做的工作,是指在一个渲染周期(通常为 16.7ms,对应 60fps 的刷新率)内完成的一系列任务,以确保页面流畅地显示和响应用户交互。这个过程是浏览器实现高性能动画和响应式 UI 的核心机制。时间切片任务就是把任务放到对应帧内。
1. 处理输入事件(Input Event Handling)
- 处理用户的操作:点击、滚动、触摸、键盘输入等。
- 这些事件可能会触发 JavaScript 回调函数。
⚠️ 如果 JS 阻塞太久,会导致这一帧跳过或延迟,出现卡顿。
2. 执行 JavaScript
- 执行绑定在这一帧中的 JS 逻辑(比如
requestAnimationFrame
中的回调)。 - 可能会修改 DOM 或 CSS 样式,从而导致后续布局/绘制变化。
requestAnimationFrame
的作用: JavaScript 中可以使用requestAnimationFrame()
来注册在下一帧开始前运行的回调函数。它用于:
- 动画更新
- 性能优化(避免频繁 layout 和 paint)
- 同步视觉变化与浏览器渲染节奏
3. 样式计算(Style Calculation)
- 将 CSS 规则应用到对应的元素上。
- 计算出每个元素最终的样式值(computed style)。
4. 布局(Layout / Reflow)
- 确定每个元素在页面上的几何位置和大小。
- 是一个相对昂贵的操作,尤其是当文档结构复杂时。
5. 绘制(Paint)
- 将元素绘制成像素点,生成可视化的图像。
- 包括文字、颜色、图像、边框、阴影等。
6. 合成(Composite)
- 将多个图层按照正确的顺序合并成最终画面。
- 最终输出到屏幕上。
8. 浏览器空闲时间任务 requestIdleCallback()
requestIdleCallback
是浏览器提供的一种API,用于主线程空闲时执行低优先级任务(宏任务)
。
浏览器FPS一般为60帧 16.7ms,会把requestIdleCallback()
任务拆解到每一帧得空闲时间内去执行。
requestIdleCallback
与 requestAnimationFrame
类似,但用途不同:
requestAnimationFrame
:用于高优先级的视觉更新(如动画),确保同步渲染。requestIdleCallback
:用于低优先级任务(如预加载、日志上报、UI 预处理等),在浏览器空闲时才执行。
<body>
<script>
let i = 0
const nums = 30000
const tasks = []
const generateDOM = () => {
for (let i = 0; i < nums; i++) {
tasks.push(function () {
const dom = document.createElement('div')
dom.innerHTML = i
document.body.appendChild(dom)
})
}
}
generateDOM()
// 1. 正常执行,一次性渲染
const onceRunFn = () => {
while (tasks.length > 0) {
const task = tasks.shift()
task()
}
}
// onceRunFn()
// 2. requestIdleCallback 空闲任务执行
const requestIdleCallbackFn = () => {
function workLoop (deadline) {
if (deadline.timeRemaining() > 1 && tasks.length > 0) {
const task = tasks.shift()
task()
}
if (tasks.length > 0) {
requestIdleCallback(workLoop)
}
}
requestIdleCallback(workLoop)
}
requestIdleCallbackFn()
</script>
</body>
📉 关键性能指标(FPS)
- 浏览器理想目标是保持每秒 60 帧(60fps)。
- 如果某帧超过 16.7ms,就会造成掉帧,用户会感觉到卡顿。
- 开发者工具(DevTools)中的 Performance 面板可以帮助分析每一帧的耗时和瓶颈。
✅ 如何优化一帧性能?
- 减少 JS 执行时间:避免长任务,拆分复杂逻辑
- 减少 Layout 和 Paint:使用 transform、opacity 等不会触发布局的属性
- 使用防抖/节流:控制高频事件的触发频率
- 利用 Web Worker:把非 UI 相关任务移出主线程