浏览器渲染原理
浏览器解析
- 浏览器通过请求的url进行域名解析,向服务器发起请求,接受文件(html,css,js,image等)。
- html文件加载后,开始构建DOM Tree。
- css样式文件加载后,开始解析和构建CSS Rule Tree。
- Javascript脚本文件加载后,通过DOM API和CSSOM API 来操作DOM Tree和CSS Rule Tree。
浏览器渲染
- 浏览器引擎通过DOM Tree和CSS Rule Tree 构建Rendering Tree。
- Rending Tree 并不与DOM Tree对应,比如像
<head>
标签内容或带有display:none;
的元素节点并不包括在Rendering Tree中。 - 通过CSS Rule Tree匹配DOM Tree进行定位坐标和大小,是否换行,以及
position,overflow,z-index
等等属性,这个过程称为Flow
或Layout
。 - 最终是通过Native GUI 的API绘制网页画面的过程称为Paint。
渲染流程总结
创建DOM树 -> 创建cssom树 -> 执行脚本 -> 生成渲染树 -> 生成布局 -> 绘制
一旦Render tree构建完毕后,浏览器就根据render tree来绘制页面。浏览器在绘制页面的过程中不断进行重绘和回流。
重绘(repaint)与回流(reflow)
重绘(repaint)
当Render Tree中的一些元素需要更新属性,当这些属性只会影响元素的外观,风格,而不会影响到元素的布局,此类的页面渲染就叫做页面重绘。
回流(reflow)
当Render Tree中的一部分或者全部因为元素的规模尺寸,布局,隐藏等改变而引起的页面重新渲染,叫做回流。回流必引起重绘,重绘不一定会引起回流。
何时会发生回流(reflow)
有大量用户行为以及潜在的DHTML改变会触发回流(reflow)。例如,改变浏览器窗口的大小,使用一些Javascript方法,包括计算样式,对DOM进行元素的添加或者删除,或是改变元素的class等。
- 调整窗口大小(Resizing the window)
- 改变字体(Change the font)
- 增加或者移除样式表(Adding or removing a stylesheet)
- 内容变变化,比如用户在input中输入文字(Content changes , such as a user typing text in an input box)
- 激活css伪类,比如
:hover
(IE中卫兄弟节点伪类的激活)(Activation of css pseudo such as :hover (in IE the activation of the pesudo cloass of a sibling))- 操作class属性(Manipulation the class attribute)
- 脚本操作DOM(A script manipulating the DOM)
- 计算
offsetWidth
和offsetHeight
属性(Calculating offsetWidth and offsetHeight)- 设置style属性的值(setting a property of the style attribute)
性能优化
回流比重绘的代价更高,回流的花销跟render tree有多少节点需要重新构建有关系,浏览器本身能进行优化,尽可能较少重绘和回流。如果每行javascript代码操作DOM都需要回流的话,浏览器可能就会受不了,所以很多浏览器都会优化这些操作,浏览器维护1个队列,把所有会引起回流,重绘的操作放入这个队列,等待队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让多次的回流、重绘变成了一次回流重绘。
当你向浏览器请求一些style信息时,就会让浏览器flush队列,比如:
- offsetTop,offsetLeft,offsetWidth,offsetHeight
- scrollTop/left/width/height
- clientTop/left/width/height
- width,height
- 请求了getComputedStyle(),或者IE的currentStyle。
当请求了上面的一些属性的时候,浏览器为了给你最精确的值,需要flush队列,因为队列中可能会有影响到这些值的操作。即使你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。
如何减少回流、重绘
css中避免回流
如果想设定元素的样式,通过改变元素的class名(尽可能在DOM树的最末端)
回流可以自上而下、或自下而上的回流信息传递给周围节点。回流是不可避免的,但是可以减少其影响。尽可能在DOM树的里面改变class,可以限制回流的范围,使其影响尽可能少的节点。例如,应该避免通过改变对包装元素类去影响子节点的现实。面向对象的css始终尝试获得他们影响的类对象(DOM节点或节点),但在这种情况下,他已尽可能的减少了回流的影响,增加性能的优势。
避免设置多层内联样式
我们都知道与DOM交互很慢,我们尝试在一种无形的DOM树片段组进行更改,然后整个改变应用到DOM上又导致一个回流。同样,通过style属性设置样式也会导致回流。避免设置多级内联,因为每个改变都会造成回流,央视应该合并在一个外部类,这样当元素的class属性可被操控师仅会产生一个reflow。
动画效果英东到position属性为absolute或fixed的元素上
动画效果应用到position属性为absolute或fixed元素上,他们不影响其他元素的布局,所以他们只会重新绘制,而不是一个完整的回流,这样消耗更低。
牺牲平滑度获取速度
Opera建议我们牺牲平滑度获取速度,其意思可能是指想每次1像素移动一个动画,但是如果此动画及随后的回流使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。动画元素每次移动3像素可能在非常快的机器上看起来平滑度低了,但他不会导致cpu在较慢的机器和移动设备中抖动。
避免使用table布局
避免使用table布局,在布局完全建立之前,table经常需要多个关口,因为table是个很罕见的可以影响在他们之前已经进入DOM元素的显示元素。想象一下,因为表格最后一个单元格的内容过宽而导致纵列大小完全改变。这就是为什么所有的浏览器都逐步地不支持表格的渲染。而有另外一个原因为什么表格布局时很糟糕的主意,根据Mozilla,即使一些小的变化将导致表格(table)中的所有其他节点回流。
避免使用css的javascript表达式
这项规则较过时,但确实是个好的主意。主要的原因,这些表现是如此昂贵,是因为他们每次重新计算文档,或部分文档、回流。正如我们从所有的很多事情看到的:引发回流,它可以每秒产生成千上万次。
javascript避免回流
- 避免逐项更改样式,最好一次性更改style属性,或者将样式列表定义为class并一次性更改class属性。
- 避免循环操作DOM。创建一个documentFragment或div,在他上面应用所有DOM操作,最后把他添加到window.document。
- 也可以在一个display:none的元素上进行操作,最终把他显示出来。因为display:none上的DOM操作不会引发回流和重绘。
- 避免循环读取offsetLeft等属性,在循环之前把他们存起来。
- 绝对定位具有复杂动画的元素。绝对定位使他脱离文档流,否则会引起父元素以及后续元素大量的回流。