Appearance
关键渲染路径
(JS、CSS)触发视觉变化 => 样式计算 => 局部(重排) => 绘制(重绘) => 合成
重绘
当改变元素的外观时不触发布局更新,只触发样式计算和绘制。
color
background-color
重排(回流)
当浏览器必须重新处理和绘制部分或全部页面时,回流就会发生。
页面初始渲染
添加或删除可见 DOM
改变元素位置
改变元素尺寸(width、height、margin、border)
改变元素内容(文本变化或图片大小的变化)
改变窗口尺寸
获取位置相关信息
offsetTop/Left/Width/Height
clientTop/Left/Width/Height
scrollTop/Left/Width/Height
getComputedStyle/currentStyle
width/height
重绘和重排的关系
重绘不会引起重排,但重排一定会引起重绘,一个元素的重排通常会带来一系列的反应,甚至触发 整个页面的重排和重绘,性能代价是高昂的。
重排优化
浏览器自身的渲染队列机制,发现某一行要修改元素样式,不立即渲染,而是看下一行,如果 下一行也会改变样式,则把修改样式的操作放到渲染队列中,再往下看,等到一定数量或一定 时间间隔再再整体渲染一次,引发一次重绘/重排。
将多次改变样式属性的操作合并成一次操作,使用类名或 cssText。
如果要批量添加 DOM,使用 fragment 或 HTMLStr。
将需要多次重排的元素(如开启动画的元素),position 属性设为 absolute 或 fixed 或 sticky 脱离了文档流。
由于 display 属性为 none的 元素不在 Render Tree 中,如果要对一个元素进行复杂的操作 时,可以先隐藏它,操作完成后再显示。这样只触发两次重排。
缓存布局信息,由于读取布局信息会导致回流,因此用变量保存这些信息避免之后不必要的回流。
缓存布局信息
html
<body>
<div id="app"></div>
<script>
const reFlow = () => {
const app = document.getElementById('app')
// 先缓存布局信息
const { offsetTop } = app
const div = document.createElement('div')
div.innerHTML = 'foo'
app.appendChild(div)
// 不会触发重排
console.log(offsetTop)
// 会触发重排,因为新 DOM 被添加,浏览器的布局信息已经过期(dirty)。
// 虽然 offset 不变,在更改了 DOM 后读取布局信息浏览器会强制刷新布局(reflow),
// 保证拿到最新的数值。
console.log(app.offsetTop)
}
window.addEventListener('load', () => {
reFlow()
})
</script>
</body>所以要读写分离,现在外面缓存好。
布局抖动问题
在 JavaScript 代码中,如果频繁交替进行 DOM 读写操作,尤其是反复读取会触发布局(如 offsetTop、clientHeight 等)和修改 DOM(如 appendChild、style 赋值),每次读取布局属性时,浏览器都必须执行一次强制同步布局(Reflow)。如果这种模式在循环或高频事件中出现,就会导致性能严重下降,这种现象就被称为布局抖动(Layout Thrashing)。