浏览器渲染原理

浏览器渲染工作流程如图:


第一步、浏览器会解析三个东西(左边三个浅绿色底区块):

1、HTML/SVG/XHTML,事实上,Webkit有三个C++的类对应这三类文档。解析这三种文件会产生一个DOM Tree。
2、CSS,解析CSS会产生规则树。
3、JavaScript,脚本,主要是通过DOM API和CSS DOM API来操作DOM Tree和CSS Rule Tree。

第二步、解析完成后,浏览器引擎会通过DOM Tree和CSS Rule Tree来构造 Rendering Tree。

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

第三步、通过调用操作系统Native GUI的API绘制。

接下来具体看各个步骤的详细分解:

1、DOM解析
HTML的DOM Tree解析如下:
<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会解析成这样:

下面是另一个有SVG标签的情况

2、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*/ [] {font-style: italic;}

于是我们的CSS Rule Tree 会是这样:
注意,图中的第四条规则出现了两次,一次是独立的,一次是在规则3的子节点。所以,我们可以知道,建立CSS Rule Tree是需要比照着DOM Tree来的。CSS匹配DOM Tree主要是从右到左解析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节点上。

3、渲染
渲染的流程基本上如下(黄色的四个步骤):
a、计算CSS样式
b、构建Render Tree
c、Layout 定位坐标和大小,是否换行,各种postion,overflow,z-index属性...
d、正式绘制

注意,上图流程中有很多连接线,这表示了JavaScript动态修改了DOM属性或是CSS属性会导致重新Layout,有些改变不会,就是那些看不到的箭头,比如,修改后的CSS rule没有匹配到等。

这里重要的两个概念,一个是Reflow,另一个是Repaint。不一样
Repaint:屏幕的一部分要重画,比如某个CSS的背景色变了,但是元素的几何尺寸没有变。
Reflow:意味着元件的几何尺寸变了,需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout。( HTML使用的是flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫reflow),reflow会从<html>这个root frame开始递归往下,依次计算所有的节点几何尺寸和位置,在reflow过程中,可能会增加一些frame,比如一个文本字符串必须被包装起来。
备注:在HTML初始化的时候也会做一次reflow,叫intial reflow。

Reflow的成本比Repaint的成本高得多。DOM Tree里的每个节点都会有reflow方法,一个节点的reflow很有可能导致子节点,甚至父节点以及同级节点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。
所以,下面这些动作有很大可能会是成本比较高的。
当你增加、删除、修改DOM节点时,会导致Reflow或Repaint。
当你移动DOM的位置,或是搞个动画的时候。
当你修改CSS样式的时候
当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
当你修改网页的默认字体时。
注:display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。
再补充一些关于滚屏的事,通常来说,如果在滚屏的时候,我们的页面上的所有的像素都会跟着滚动,那么性能上没什么问题,因为我们的显卡对于这种把全屏像素往上往下移的算法是很快。但是如果你有一个fixed的背景图,或是有些Element不跟着滚动,有些Element是动画,那么这个滚动的动作对于浏览器来说会是相当痛苦的一个过程。你可以看到很多这样的网页在滚动的时候性能有多差。因为滚屏也有可能会造成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 = "#ffaadd";  //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值:
1、offsetTop, offsetLeft, offsetWidth, offsetHeight
2、scrollTop/Left/Width/Height
3、clientTop/Left/Width/Height
4、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: " + "px;";

2)把DOM离线后修改。如:
a、使用documentFragment对象在内存里操作DOM。
b、先把DOM给display:none(有一次repaint),然后你想怎么改就怎么改。比如修改100此,然后再把他显示出来。
c、clone一个DOM节点到内存里,然后想怎么改就怎么改,改完后,和在线的那个交换一下。

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

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

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

6)  千万不要使用table布局。因为可能很小的一个小改动会造成整个table的重新布局。

7)  固定布局相对自动布局相比之下会高效




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值