浏览器收到 HTML 后构建 DOM,加载并解析样式,计算元素样式与几何布局,再绘制和合成可显示的结果。实际实现会并行、缓存或跳过一些工作,这条流程用于理解依赖,不能当成每一帧都完整执行的流水线。
修改一个宽度会发生什么
改变元素宽度可能影响兄弟元素、换行和页面高度,需要重新布局;改变颜色通常不需要改几何位置,但仍可能绘制。transform、opacity 在合适条件下可以减少主线程工作,但是否单独合成、消耗多少显存,要看真实页面和浏览器。不要给所有元素加 will-change,额外图层同样有成本。
DOM 与 CSSOM 的变化不一定立刻反映到屏幕。浏览器常会合并工作;读 offsetWidth、getBoundingClientRect 等几何信息时,若此前样式已失效,可能迫使它先计算样式和布局。循环里交错写和读容易重复触发这类同步工作。
// 假设 boxes 是一组已挂到页面上的元素。
const widths = boxes.map(box => box.getBoundingClientRect().width);
boxes.forEach((box, index) => {
box.style.width = widths[index] + 1 + "px";
});
先读后写避免了这段代码自身的交错,不意味着消除了所有布局。读取的坐标也是某一时刻的结果;字体加载、图片解码、窗口变化还可能再次改变尺寸。
在开发者工具里观察
渲染实验页 提供“交错读写”和“先读后写”两种操作。录制 Performance,分别运行并查看样式计算、Layout 与脚本耗时。页面显示的同步调用耗时只覆盖测量区间,不等于整次绘制完成时间,更不等于用户真实体验指标。
requestAnimationFrame 的回调安排在合适的渲染机会之前,适合协调动画更新。回调本身仍能阻塞主线程;在后台标签页还可能被限速。不要用两个空 rAF 之间的时间证明页面一定流畅,也不要在滚动回调里反复测量所有元素。
长列表先减少实际渲染数量或采用虚拟化;图片写明尺寸以预留空间;动画尽量减少大范围几何变化。优化前记录触发动作、数据量、浏览器、设备与录制结果,优化后重做同一动作。
调试练习
把实验页的盒子数从 100 改为 1000,重复录制至少三次。分清脚本计时、布局耗时和最终画面更新的边界。再给盒子加长文本,观察一个宽度变化是否影响后面的高度与位置。
面试自查:重排与重绘是什么关系?为什么读取布局信息也可能变慢?合成线程能否解决一个耗时很长的 JavaScript 循环?