浏览器渲染原理

浏览器工做大体流程

废话少说,先来看个图:php

从上面这个图中,咱们能够看到那么几个事:css

1)浏览器会解析三个东西:html

一个是HTML/SVG/XHTML,事实上,Webkit有三个C++的类对应这三类文档。解析这三种文件会产生一个DOM Tree。
CSS,解析CSS会产生CSS规则树。
Javascript,脚本,主要是经过DOM API和CSSOM API来操做DOM Tree和CSS Rule Tree.算法

2)解析完成后,浏览器引擎会经过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。注意:浏览器

Rendering Tree 渲染树并不等同于DOM树,由于一些像Header或display:none的东西就不必放在渲染树中了。
CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每一个Element。也就是DOM结点。也就是所谓的Frame。
而后,计算每一个Frame(也就是每一个Element)的位置,这又叫layout和reflow过程。app

3)最后经过调用操做系统Native GUI的API绘制。异步

DOM解析
HTML的DOM Tree解析以下:工具

<html>
<html> <head> <title>Web page parsing</title> </head> <body> <div> <h1>Web page parsing</h1> <p>This is an example Web page.</p> </div> </body> </html>

上面这段HTML会解析成这样:布局

CSS解析

CSS的解析大概是下面这个样子(下面主要说的是Gecko也就是Firefox的玩法),假设咱们有下面的HTML文档:性能

<doc>
<title>A few quotes</title>
<para>
  Franklin said that <quote>"A penny saved is a penny earned."</quote>
</para>
<para>
  FDR said <quote>"We have nothing to fear but <span>fear itself.</span>"</quote>
</para>
</doc>

因而DOM Tree是这个样子:

而后咱们的CSS文档是这样的:

/* rule 1 */ doc { display: block; text-indent: 1em; }
/* rule 2 */ title { display: block; font-size: 3em; }
/* rule 3 */ para { display: block; }
/* rule 4 */ [class="emph"] { font-style: italic; }

因而咱们的CSS Rule Tree会是这个样子:

注意,图中的第4条规则出现了两次,一次是独立的,一次是在规则3的子结点。因此,咱们能够知道,创建CSS Rule Tree是须要比照着DOM Tree来的。CSS匹配DOM Tree主要是从右到左解析CSS的Selector,好多人觉得这个事会比较快,其实并不必定。关键还看咱们的CSS的Selector怎么写了。

注意:CSS匹配HTML元素是一个至关复杂和有性能问题的事情。因此,你就会在N多地方看到不少人都告诉你,DOM树要小,CSS尽可能用id和class,千万不要过渡层叠下去,……

经过这两个树,咱们能够获得一个叫Style Context Tree,也就是下面这样(把CSS Rule结点Attach到DOM Tree上):

因此,Firefox基本上来讲是经过CSS 解析 生成 CSS Rule Tree,而后,经过比对DOM生成Style Context Tree,而后Firefox经过把Style Context Tree和其Render Tree(Frame Tree)关联上,就完成了。注意:Render Tree会把一些不可见的结点去除掉。而Firefox中所谓的Frame就是一个DOM结点,不要被其名字所迷惑了。

注:Webkit不像Firefox要用两个树来干这个,Webkit也有Style对象,它直接把这个Style对象存在了相应的DOM结点上了。

渲染

渲染的流程基本上以下(黄色的四个步骤):

  • 计算CSS样式
  • 构建Render Tree
  • Layout – 定位坐标和大小,是否换行,各类position, overflow, z-index属性 ……
  • 正式开画

注意:上图流程中有不少链接线,这表示了Javascript动态修改了DOM属性或是CSS属会致使从新Layout,有些改变不会,就是那些指到天上的箭头,好比,修改后的CSS rule没有被匹配到,等。

这里重要要说两个概念,一个是Reflow,另外一个是Repaint。这两个不是一回事。

  • Repaint ——屏幕的一部分要重画,好比某个CSS的背景色变了。可是元素的几何尺寸没有变。
  • Reflow ——意味着元件的几何尺寸变了,咱们须要从新验证并计算Render Tree。是Render Tree的一部分或所有发生了变化。这就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式布局,因此,若是某元件的几何尺寸发生了变化,须要从新布局,也就叫reflow)reflow 会从这个root frame开始递归往下,依次计算全部的结点几何尺寸和位置,在reflow过程当中,可能会增长一些frame,好比一个文本字符串必需被包装起来。

Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每一个结点都会有reflow方法,一个结点的reflow颇有可能致使子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,可是若是reflow发生在手机上,那么这个过程是很是痛苦和耗电的。
因此,下面这些动做有很大可能会是成本比较高的。

  • 当你增长、删除、修改DOM结点时,会致使Reflow或Repaint
  • 当你移动DOM的位置,或是搞个动画的时候。
  • 当你修改CSS样式的时候。
  • 当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
  • 当你修改网页的默认字体时。
  • 注:display:none会触发reflow,而visibility:hidden只会触发repaint,由于没有发现位置变化。

多说两句关于滚屏的事,一般来讲,若是在滚屏的时候,咱们的页面上的全部的像素都会跟着滚动,那么性能上没什么问题,由于咱们的显卡对于这种把全屏像素往上往下移的算法是很快。可是若是你有一个fixed的背景图,或是有些Element不跟着滚动,有些Elment是动画,那么这个滚动的动做对于浏览器来讲会是至关至关痛苦的一个过程。你能够看到不少这样的网页在滚动的时候性能有多差。由于滚屏也有可能会形成reflow。

基本上来讲,reflow有以下的几个缘由:

  • Initial。网页初始化的时候。
  • Incremental。一些Javascript在操做DOM Tree时。
  • Resize。其些元件的尺寸变了。
  • StyleChange。若是CSS的属性发生变化了。
  • Dirty。几个Incremental的reflow发生在同一个frame的子树上。

好了,咱们来看一个示例吧:

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!'));

固然,咱们的浏览器是聪明的,它不会像上面那样,你每改一次样式,它就reflow或repaint一次。通常来讲,浏览器会把这样的操做积攒一批,而后作一次reflow,这又叫异步reflow或增量异步reflow。可是有些状况浏览器是不会这么作的,好比:resize窗口,改变了页面默认的字体,等。对于这些操做,浏览器会立刻进行reflow。

可是有些时候,咱们的脚本会阻止浏览器这么干,好比:若是咱们请求下面的一些DOM值:

offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
IE中的 getComputedStyle(), 或 currentStyle

由于,若是咱们的程序须要这些值,那么浏览器须要返回最新的值,而这样同样会flush出去一些样式的改变,从而形成频繁的reflow/repaint。

减小reflow/repaint

下面是一些Best Practices:

1)不要一条一条地修改DOM的样式。与其这样,还不如预先定义好css的class,而后修改DOM的className。

// bad
var left = 10,
top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";

// Good
el.className += " theclassname";

// Good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

2)把DOM离线后修改。如:

使用documentFragment 对象在内存里操做DOM
先把DOM给display:none(有一次reflow),而后你想怎么改就怎么改。好比修改100次,而后再把他显示出来。
clone一个DOM结点到内存里,而后想怎么改就怎么改,改完后,和在线的那个的交换一下。

3)不要把DOM结点的属性值放在一个循环里当成循环里的变量。否则这会致使大量地读写这个结点的属性。

4)尽量的修改层级比较低的DOM。固然,改变层级比较底的DOM有可能会形成大面积的reflow,可是也可能影响范围很小。

5)为动画的HTML元件使用fixed或absoult的position,那么修改他们的CSS是不会reflow的。

6)千万不要使用table布局。由于可能很小的一个小改动会形成整个table的从新布局。

几个工具和几篇文章

有时候,你会也许会发如今IE下,你不知道你修改了什么东西,结果CPU一会儿就上去了到100%,而后过了好几秒钟repaint/reflow才完成,这种事情以IE的年代时常常发生。因此,咱们须要一些工具帮咱们看看咱们的代码里有没有什么不合适的东西。

Chrome下,Google的SpeedTracer是个很是强悍的工做让你看看你的浏览渲染的成本有多大。其实Safari和Chrome均可以使用开发者工具里的一个Timeline的东东。
Firefox下这个基于Firebug的叫Firebug Paint Events的插件也不错。
IE下你能够用一个叫dynaTrace的IE扩展。
最后,别忘了下面这几篇提升浏览器性能的文章:

Google – Web Performance Best Practices
Yahoo – Best Practices for Speeding Up Your Web Site
Steve Souders – 14 Rules for Faster-Loading Web Sites
How Browsers Work