浏览器渲染原理

当我们打开一个浏览器,输入一个url,按下回车,不用多久,一个漂亮的页面就呈现在我们眼前了。其实,在我们等待页面打开的这段时间里,浏览器可是做了不少的事情,到底是什么事情呢,就我们一起来看看吧

首先,我们需要先了解一下什么是页面渲染以及页面渲染的整个过程~

 

1、页面渲染

简单地说,页面渲染就是浏览器将 HTML 代码根据 CSS 定义的规则显示在浏览器窗口中的这个过程

2、渲染过程

1. 用户输入网址(假设是个 HTML 页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回 HTML 文件;

2. 浏览器开始载入 HTML 代码,发现 <head> 标签内有一个 <link> 标签引用外部 CSS 文件;

3. 浏览器又发出 CSS 文件的请求,服务器返回这个 CSS 文件;

4. 浏览器继续载入 HTML 中 <body> 部分的代码,并且 CSS 文件已经拿到手了,可以开始渲染页面了;

5. 浏览器在代码中发现一个 <img> 标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;

6. 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;

7. 浏览器发现了一个包含一行 JavaScript 代码的 <script> 标签,赶快运行它;

8. JavaScript 脚本执行了这条语句,它命令浏览器隐藏掉代码中的某个 <div>(style.display=”none”)。杯具啊,突然就少了这么一个元素,浏览器不得不重新渲染这部分代码;

9. 终于等到了 </html> 的到来,浏览器泪流满面……

10. 等等,还没完,用户点了一下界面中的“换肤”按钮,JavaScript 让浏览器换了一下 <link> 标签的 CSS 路径;

11. 浏览器召集了在座的各位 <div><span><ul><li> 们,“大伙儿收拾收拾行李,咱得重新来过……”,浏览器向服务器请求了新的CSS文件,重新渲染页面。

 

现在大家已经知道什么是页面渲染了吧,接下来再介绍两个概念,reflow和repaint~

3、reflow(回流)

 

首先说一下页面为什么会慢?那是因为浏览器要花时间、花精力去渲染,尤其是当它发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow

下面这张图很清晰的说明了这个过程

reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

 

当然,reflow 问题是可以优化的,我们可以尽量减少不必要的 reflow。比如开头的例子中的 <img> 图片载入问题,这其实就是一个可以避免的 reflow —— 给图片设置宽度和高度就可以了。这样浏览器就知道了图片的占位面积,在载入图片前就预留好了位置。

4、repaint(重绘)

另外,有个和 reflow 看上去差不多的术语:repaint,中文叫重绘。如果只是改变某个元素的背景色、文 字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器 repaint。repaint 的速度明显快于 reflow(在IE下需要换一下说法,reflow 要比 repaint 更缓慢)。

 

学习了上面的知识后,相信大家对浏览器渲染已经有了一个比较深刻的了解了吧,接下来让我们进行更深入的学习

5、浏览器工作大致流程

从上面这个图中,我们可以看到那么几个事:

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

  • 一个是 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 过程。

  3)最后通过调用操作系统 Native GUI 的 API 绘制。

6、DOM解析

HTML 的 DOM Tree 解析如下:

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

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

7、CSS 解析

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

于是 DOM Tree 是这个样子:

然后我们的 CSS 文档是这样的:

于是我们的 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 结点,不要被其名字所迷惑了

 

8、重说渲染

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

(1)计算 CSS 样式

(2)构建 Render Tree

(3)Layout – 定位坐标和大小,是否换行,各种 position, overflow, z-index 属性 ……

(4)正式开画

注意:上图流程中有很多连接线,这表示了 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,比如一个文本字符串必需被包装起来。
  • 所以,下面这些动作有很大可能会是成本比较高的。

    • 当你增加、删除、修改 DOM 结点时,会导致 Reflow 或 Repaint。
    • 当你移动 DOM 的位置,或是搞个动画的时候。
    • 当你修改 CSS 样式的时候。
    • 当你 Resize 窗口的时候(移动端没有这个问题),或是滚动的时候。
    • 当你修改网页的默认字体时。

      注:display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,因为没有发现位置变化。

 

9、一些优化建议

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

 

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

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

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

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

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

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

posted @ 2016-04-08 19:51  Yellow_ice  阅读(436)  评论(0编辑  收藏  举报