浏览器的重绘与回流

这是我在之前面试中遇到的一个问题,今天回想起来正好做一个总结

浏览器在将页面展示给我们之前都做了什么(渲染机制)

这其实也是我遇到的一道面试题,与重绘和回流息息相关

  1. 用户输入网址
  2. 浏览器通过DNS获取网站的IP地址
  3. 浏览器尝试与服务器建立连接
  4. 服务器发送永久重定向
  5. 浏览器跟踪重定向地址
  6. 服务器处理请求
  7. 服务器发送HTML响应
  8. 浏览器接收响应,开始解析
  9. 解析html文件,处理并创建DOM
  10. 解析css样式表, 构建CSSOM
  11. DOMCSSOM进行结合,构建渲染树(Render Tree)
  12. 根据渲染树来布局(layout),计算每一个节点的位置
  13. 调用GPU进行绘制(Paint),合成图层
  14. 重绘(repaint)与回流(reflow)

html解析遇到script时会暂停构建DOM,执行js脚本,执行完毕后才会继续构建DOM树,因此将js文件放在html文件底部进行加载才会节省加载时间,加快绘制速度

浏览器使用流式布局 (Flow Based Layout),对Render Tree的计算通常只需要遍历一次就可以完成,但table及其内部元素除外,他们可能需要多次计算,通常要花3倍于同等元素的时间,这也是为什么要避免使用table布局的原因之一


重绘与回流

回流必将引起重绘,而重绘不一定引起回流

回流

Render Tree中部分或全部元素的尺寸、结构、布局、隐藏等发生改变而需要重新构建,浏览器因此重新渲染的过程叫做回流
会导致回流的操作:

  • 页面的首次渲染
  • 浏览器窗口哦的大小发生改变
  • 元素的尺寸或位置发生改变
  • 元素的内容发生变化
  • 元素样式发生改变(字体、隐藏等)
  • 激活CSS伪类(:after,:before,:hover等)
  • 调用某些方法

常用且会导致回流的属性和方法:

  • clientWidthclientHeightclientTopclientLeft
  • offsetWidthoffsetHeightoffsetTopoffsetLeft
  • scrollWidthscrollHeightscrollTopscrollLeft
  • scrollIntoView()scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo

重绘

当页面元素的样式发生改变且并不影响它在文档中的位置时,浏览器将根据其新属性进行重新绘制,这个过程就是重绘

性能影响

回流比重绘更加影响性能,付出代价更高

有时即使仅仅回流一个单一的元素,它的父元素以及任何跟随它的元素也会产生回流。

现代浏览器会对频繁的回流或重绘操作进行优化:

浏览器会维护一个队列,把所有引起回流和重绘的操作放入队列中,如果队列中的任务数量或者时间间隔达到一个阈值的,浏览器就会将队列清空,进行一次批处理,这样可以把多次回流和重绘变成一次。

避免的方法

css

  • 避免使用table布局
  • 尽可能在DOM的末端改变class
  • 避免使用多层内联样式
  • 将动画效果应用到position属性为absolutefixed的元素上
  • 避免使用css表达式(例如:calc()

Js

  • 避免频繁地操作样式,尽可能地一次性重写style属性
  • 避免频繁操作DOM,可以创建一个documentFragment,在其上完成所有DOM操作,然后将其加入到文档中
  • 也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
  • 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。