回流和重绘
回流和重绘是指当HTML或CSS节点发生变化时,浏览器会根据变化的节点重新绘制或者重新布局。回流和重绘会导致浏览器性能降低。
回流 (Reflow / 重排)
回流是指浏览器为了重新计算部分或全部元素的几何位置和大小,而重新构建渲染树的过程。回流必定会触发重绘,它是浏览器渲染中最消耗性能的操作之一。
触发条件
DOM 树变动: 添加或删除可见的 DOM 元素。
元素尺寸或位置变化: 修改 width、height、padding、margin、border、position (如 top, left) 等。
内容发生变化: 文本内容替换、字体大小 (font-size) 改变、图片被不同尺寸的图片替换等。
窗口尺寸改变: 浏览器窗口调整大小 (Resize)。
读取特定的布局属性: 当你使用 JavaScript 读取 offsetWidth、clientHeight、scrollTop 或调用 getComputedStyle() 时,为了保证返回值的准确性,浏览器会被迫清空渲染队列,立即执行一次同步的回流。
性能陷阱
在一个循环中不断读取元素的 offsetWidth 然后立刻修改它的 width。这会导致浏览器在每一轮循环都进行回流,帧率会瞬间暴跌。
重绘 (Repaint)
重绘是指元素的外观、风格发生改变,但不影响其在文档流中的几何位置和大小时,浏览器将新样式赋予元素的过程。
触发条件
修改颜色相关属性:color、background-color。
修改视觉可见性:visibility: hidden (注意 display: none 会触发回流)。
修改装饰属性:border-style、border-radius、box-shadow 等。
光栅化成本
如果高频更新一个元素的背景色或阴影,CPU 需要不断地将新的绘图指令转换为像素。对于复杂的图形(如多重 box-shadow 或大面积的渐变色),这个过程会大量占用主线程的计算资源。
GPU 硬件加速
为了打破主线程的性能瓶颈,现代浏览器引入了合成层 (Compositor Layer) 的概念。
可以把普通的网页想象成一幅画在一个画布上的油画(普通文档流)。如果你想移动画面中的一辆车,你需要把车擦掉(引发重绘),然后在旁边重新画一辆(引发重排和重绘)。
而GPU 硬件加速,相当于把这辆车画在一块透明的玻璃(独立的合成层)上,然后把这块玻璃悬浮在原画布之上。 现在你要移动这辆车,只需要直接平移这块玻璃即可。不需要擦除,不需要重画,完全不影响底层的画布。 这就是 Composite 阶段的工作,由 GPU 高效完成。
Transform:矩阵运算的魔法
当使用 transform: translate(x, y) 移动一个元素时:
- 该元素会被提升到一个独立的合成层。
- 主线程的 Layout 和 Paint 阶段被完全跳过。
- 浏览器直接将这个图层作为纹理 (Texture) 上传给 GPU。
- GPU 利用其极强的矩阵运算能力,在极短的时间内改变纹理的渲染位置。
实际应用
在实现拖拽时间轴滑块、或者在地图上让一个运动轨迹点平滑移动时,绝对不能使用 left/top,必须使用 transform。
Opacity:GPU 的 Alpha 混合
修改 opacity 同样会跳过 Layout 和 Paint
GPU 只需要在合并图层时,调整该图层纹理的 Alpha 通道透明度,这对于 GPU 来说只是基础的像素级混合运算。
Will-Change:提前开辟专属通道
will-change 的本质是强制图层提升 (Layer Promotion)。
正常情况下,浏览器在动画开始的瞬间才去创建独立的图层并将纹理上传到 GPU,这在动画开头可能会造成轻微的掉帧(闪烁)。 will-change: transform 相当于告诉浏览器:“立刻为这个元素分配一个独立的 GPU 图层,准备好显存,它马上要动了!”
内存代价
每一个合成层都需要占用宝贵的显存 (VRAM)。在移动端设备或小程序环境中,内存资源非常紧张。如果将 will-change 盲目应用在大量元素上(例如轨迹上的成百上千个点),会导致内存暴涨,应用可能会被系统强杀(OOM 崩溃)。
页面渲染
过程
- 解析HTML,形成HTML DOM树,解析CSS,生成CSS规则树。
- 将HTML DOM树与CSS规则树结合,生成Render树。
- 布局Render树。
- 绘制Render树。
- 浏览器将各层信息发送给GPU,GPU将各层合成,显示在屏幕上。
流程图
🤔 强制同步布局 (Forced Synchronous Layout)
通常是因为先“写”了样式,紧接着立刻去“读”布局属性。(页面还没来得急布局,就去读布局属性)
浏览器的“偷懒”哲学(批处理) 正常情况下,浏览器不会改一行代码就立刻去重新渲染一次,它会把这些修改操作“攒”在一个队列里。等到当前这波 JS 代码执行完,或者到了浏览器的下一帧渲染时间,它再把攒下来的修改一次性批量处理,执行一次回流和重绘。这叫异步渲染。
什么是“强制同步布局”? “强制同步布局”就是你写了一段代码,打破了浏览器的这种批处理机制,逼着它立刻停下手中的活,当场进行一次全局的排版计算。
🤔 will-change 使用时机
will-change 应该被当成一种临时状态,而不是常驻样式。
在元素真正准备开始动画前的一瞬间。比如手指刚触摸到时间轴滑块 (touchstart),或者鼠标悬停时 (mouseenter)。
动画一结束 (transitionend 或 touchend),必须立刻把它移除,把宝贵的内存还给系统。
@keyboarder-yang