Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
回流一定会重绘,重绘不一定会引起回流。
重绘Repaint
当渲染树中的元素外观(如:颜色)发生改变,不影响布局时,产生重绘
回流Reflow
最“贵”的操作:改变 DOM 元素的几何属性
- 当渲染树中的元素的布局(如:尺寸、位置、隐藏/状态状态)发生改变时,产生重绘回流。常见的几何属性有
width
、height
、padding
、margin
、left
、top
、border
等等。
-
JS获取Layout属性值(如:
offsetLeft
、scrollTop
、getComputedStyle
等)也会引起回流。因为浏览器需要通过回流计算最新值【获取特定layout属性值】:
offsetTop
、offsetLeft
、offsetWidth
、offsetHeight
、scrollTop
、scrollLeft
、scrollWidth
、scrollHeight
、clientTop
、clientLeft
、clientWidth
、clientHeight
如何最小化重绘(repaint)和回流(reflow)
- 需要要对元素进行复杂的操作时,可以先隐藏(
display:"none"
),操作完成后再显示 - 需要创建多个
DOM
节点时,使用DocumentFragment
创建完后一次性的加入document
- 缓存
Layout
属性值,如:var left = elem.offsetLeft;
这样,多次使用left
只产生一次回流 - 尽量避免用
table
布局(table
元素一旦触发回流就会导致table里所有的其它元素回流) - 避免使用
css
表达式(expression
),因为每次调用都会重新计算值(包括加载页面) - 尽量使用
css
属性简写,如:用border
代替border-width
,border-style
,border-color
- 批量修改元素样式:
elem.className
和elem.style.cssText
代替elem.style.xxx
以下几个动作可能会导致性能问题:
- 改变
window
大小 - 改变字体
- 添加或删除样式
- 文字改变
- 定位或者浮动
- 盒模型
并且很多人不知道的是,重绘和回流其实也和
Eventloop
有关。
- 当
Eventloop
执行完Microtasks
后,会判断document
是否需要更新,因为浏览器是60Hz
的刷新率,每16.6ms
才会更新一次。 - 然后判断是否有
resize
或者scroll
事件,有的话会去触发事件,所以resize
和scroll
事件也是至少16ms
才会触发一次,并且自带节流功能。 - 判断是否触发了
media query
- 更新动画并且发送事件
- 判断是否有全屏操作事件
- 执行
requestAnimationFrame
回调 - 执行
IntersectionObserver
回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好 更新界面 - 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行
requestIdleCallback
回调
Flush 队列:浏览器并没有那么简单
以我们现在的知识基础,理解上面的优化操作并不难。那么现在我问大家一个问题:
let container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'
这段代码里,浏览器进行了多少次的回流或重绘呢?
“
width
、height
、border
是几何属性,各触发一次回流;color
只造成外观的变化,会触发一次重绘。”——如果你立刻这么想了,说明你是个能力不错的同学,认真阅读了前面的内容。那么我们现在立刻跑一跑这段代码,看看浏览器实际情况:
- 我们看到浏览器只进行了一次回流和一次重绘——和我们想的不一样啊,为啥呢?
- 因为现代浏览器是很聪明的。浏览器自己也清楚,如果每次 DOM 操作都即时地反馈一次回流或重绘,那么性能上来说是扛不住的。于是它自己缓存了一个 flush 队列,把我们触发的回流与重绘任务都塞进去,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。因此我们看到,上面就算我们进行了 4 次 DOM 更改,也只触发了一次
Layout
和一次Paint
。 - 所谓的“不得已”时刻:就是我们【获取特定layout属性值】。它们有很强的“即时性”。当我们访问这些属性时,浏览器会为了获得此时此刻的、最准确的属性值,而提前将 flush 队列的任务出队。
( 这里为大家截取有“Layout
”和“Paint
”出镜的片段(这个图是通过 Chrome 的 Performance
面板得到的,后面会聊到用这个东西)。)
看了上述情况,大家可能有疑惑:
既然浏览器已经为我们做了批处理优化,那我们还有必要去在意处理这些回流重绘过程吗?
答案当然是有必要:因为并不是所有浏览器都有这样的优化处理的。所以就需要我们自行手动优化,这样才能在多浏览器平台的环境中保证良好性能。因此,养成良好的编码习惯、从根源上解决问题,仍然是最周全的方法。