在《浏览器底层手记》的前三章中,我们已经完成了从二进制字节到逻辑坐标(Layout)的转化。现在,每一个元素都知道自己在网页这个“平面”上的位置。
但现代网页是三维的。当你滚动页面、看到半透明遮罩、或者点击一个带 3D 旋转动画的按钮时,浏览器如果每一帧都重新计算整个页面的布局和像素,性能会瞬间崩塌。
为了实现 60FPS 的丝滑体验,浏览器引入了电影工业中的黑科技:分层合成(Compositing) 。
【分层】解密合成引擎(Compositor)
你可以把浏览器想象成一个高度专业化的后期剪辑师。它不是在一张白纸上画画,而是将页面拆分成多个透明的“胶片”(图层),最后再把这些胶片叠在一起。
一、 为什么需要分层?(图层的意义)
如果页面是一个整体,改变其中一个小方块的颜色,整个页面都要重绘。但如果小方块在独立的图层上,浏览器只需要重新绘制这个小图层,然后更新它与其他图层的相对位置即可。
这就是“合成”的核心哲学:用空间的换取时间,用内存换取 CPU/GPU 的自由。
二、 谁能拥有“独立办公室”?(提升为层)
并不是所有的 DOM 节点都能拥有自己的图层(Layer)。浏览器会有一套复杂的策略来决定哪些节点需要被“提升”为合成层(Compositing Layer) :
-
显式提升: 拥有 3D 属性(
transform: translate3d)、will-change、或者video、canvas标签的元素。 -
遮盖层(Overlap): 如果一个普通的元素“盖”在了一个合成层上面,为了保证显示顺序正确,这个普通元素也会被迫提升为合成层。
避坑指南: 这就是著名的“层爆炸(Layer Explosion)”。如果处理不当,页面会产生成百上千个不必要的图层,瞬间吃光显存。
三、 合成线程:不受主线程干扰的“特区”
这是浏览器架构中最精妙的设计之一。
- 主线程(Main Thread): 忙于解析 HTML、执行 JS、计算布局。如果 JS 执行太久,主线程就会卡死。
- 合成线程(Compositor Thread): 它是独立的!它负责处理图层的位移、缩放和透明度。
当你滚动页面时,合成线程直接从显存中调取已经画好的图层进行偏移,完全不需要主线程参与。这就是为什么即使你的 JS 逻辑卡死了,页面的滚动条通常依然能动。
四、 瓦片化(Tiling):处理超长网页
如果一个网页有 100 屏长,浏览器会一次性把整个图层画出来吗?
当然不,那样显存会爆炸。
- 分块(Tiles): 合成线程会将一个图层拆分成许多小方格(通常是 256x256 或 512x512)。
- 优先级调度: 只有在**视口(Viewport)**附近的瓦片才会被优先进行“栅格化”(化为像素)。当你快速滚动时,那些还没画出来的白色区域,就是瓦片化还没来得及填充的痕迹。
💡 给前端开发者的硬核贴士
-
CSS 性能的等级森严:
- 修改
width/height:触发 Layout -> Paint -> Composite(最慢)。 - 修改
background-color:触发 Paint -> Composite(中等)。 - 修改
transform/opacity:只触发 Composite(最快!因为它在合成线程完成,不占主线程)。
- 修改
-
慎用
z-index: 9999: 盲目提高层级可能触发无意义的层提升,导致内存溢出。请利用 Chrome DevTools 的 Layers 面板定期巡检你的“图层大军”。
结语
合成引擎让网页动了起来,但这些“图层”目前还只是内存里的逻辑概念,或者说是一堆待命的“空白瓦片”。
真正让像素点亮屏幕的,是那台最强大的绘图机器——GPU。