浏览器渲染原理(性能优化之如何减少重排和重绘)

9,300 阅读5分钟

继续上篇《浏览器地址栏里输入URL后的全过程

前言

为什么要了解浏览器的渲染原理?了解浏览器的渲染原理有什么好处?我们做前端开发为什么非要了解浏览器的原理?直接把网页做出来,什么需求,直接一把梭,撸完收工不好吗。

但是经常会有人会问,什么是重排重绘

重排也叫回流Reflow),重绘Repaint),会影响到浏览器的性能,给用户的感觉就是网页访问慢,或者网页会卡顿,不流畅,从而使网页访问量下降。

所以,想要尽可能的避免重排重绘,就需要了解浏览器的渲染原理

浏览器工作流程

上图我们可以看出,浏览器会解析三个模块:

  • HTML,SVG,XHTML,解析生成DOM树。
  • CSS解析生成CSS规则树。
  • JavaScript用来操作DOM APICSSOM API,生成DOM TreeCSSOM API

解析完成后,浏览器会通过已经解析好的DOM TreeCSS规则树来构造 Rendering Tree

  • Rendering Tree 渲染树并不等同于DOM树,因为一些像Headerdisplay:none的东西就没必要放在渲染树中了。

  • CSSRule Tree主要是为了完成匹配并把CSS Rule附加上Rendering

  • Tree上的每个Element。也就是DOM结点,即Frame。然后,计算每个Frame(也就是每个Element)的位置,这又叫layoutreflow过程。

  • 最后通过调用操作系统Native GUIAPI绘制。

不同内核的浏览器渲染

上图是webkit内核的渲染流程,和总体渲染流程差不多,要构建HTMLDOM Tree,和CSS规则树,然后合并生成Render Tree,最后渲染。

这个是MozillaGecko渲染引擎。
总体看来渲染流程差不多,只不过在生成渲染树或者Frame树时,两者叫法不一致,webkit称之为LayoutGecko叫做Reflow

渲染顺序

  • 当浏览器拿到一个网页后,首先浏览器会先解析HTML,如果遇到了外链的css,会一下载css,一边解析HTML
  • css下载完成后,会继续解析css,生成css Rules tree,不会影响到HTML的解析。
  • 当遇到<script>标签时,一旦发现有对javascript的引用,就会立即下载脚本,同时阻断文档的解析,等脚本执行完成后,再开始文档的解析。

  • DOM树和CSS规则树已经生成完毕后,构造 Rendering Tree
  • 调用系统渲染页面。

什么情况会造成重排和重绘。

重排意味着元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout

重排因为要重新计算Render Tree,而且每一个DOM Tree都有一个reflow方法,一旦某个节点发生重排,就有可能导致子元素和父元素甚至是同级其他元素的reflow,浪费大量的时间重新验证Render Tree

因此,重排的成本要比重绘高很多。

以下操作会导致重排重绘

  • 删除,增加,或者修改DOM元素节点。
  • 移动DOM的位置,开启动画的时候。
  • 修改CSS样式,改变元素的大小,位置时,或者将使用display:none时,会造成重排;修改CSS颜色或者visibility:hidden等等,会造成重绘
  • 修改网页的默认字体时。
  • Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
  • 内容的改变,(用户在输入框中写入内容也会)。
  • 激活伪类,如:hover。
  • 计算offsetWidthoffsetHeight

如果当前网页含有一些动画,或者固定不动元素的网页时,由于滚动也会发生重排,一旦发生滚动,当前浏览器所承受的压力很大,就会造成网页的卡顿,掉帧等情况。


var bstyle = document.body.style; // cache
 
bstyle.padding = "20px"; // reflow, repaint
bstyle.border = "10px solid red"; //  再一次的 reflow 和 repaint
 
bstyle.color = "blue"; // repaint
bstyle.backgroundColor = "#fad"; // repaint
 
bstyle.fontSize = "2em"; // reflow, repaint
 
// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('dude!'));

以上逻辑,几乎每一步都会造成重排重绘,如果浏览器像这样处理的话,可能现代的浏览器没有我们使用的那么流畅了。
因此浏览器有一个机制,会把需要重排重绘的先积累着,然后一次性进行重排重绘

当然,不是所有的情况浏览器都是这样处理的,比如resize或者修改默认字体,对于这些操作,浏览器会立马进行重排

所以我们在监听resize事件时,一般我们都会做防抖节流

如何减少重排和重绘

  • 尽量避免style的使用,对于需要操作DOM元素节点,重新命名className,更改className名称。
  • 如果增加元素或者clone元素,可以先把元素通过documentFragment放入内存中,等操作完毕后,再appendChildDOM元素中。
  • 不要经常获取同一个元素,可以第一次获取元素后,用变量保存下来,减少遍历时间。
  • 尽量少使用dispaly:none,可以使用visibility:hidden代替,dispaly:none会造成重排visibility:hidden会造成重绘
  • 不要使用Table布局,因为一个小小的操作,可能就会造成整个表格的重排重绘
  • 使用resize事件时,做防抖节流处理。
  • 对动画元素使用absolute / fixed属性。
  • 批量修改元素时,可以先让元素脱离文档流,等修改完毕后,再放入文档流。

最后

参考文章:

How browsers work

浏览器的渲染原理简介

前端性能优化之重排和重绘