前端开发

浏览器渲染页面机制

2017-04-21  本文已影响0人  Destiny_漂亮的小姐姐

浏览器的渲染过程

1,浏览器解析html源码,然后创建一个 DOM树。在DOM树中,每一个HTML标签都有一个对应的节点,并且每一个文本也都会有一个对应的文本节点。DOM树的根节点就是 documentElement,对应的是html标签。

2,浏览器解析CSS代码,计算出最终的样式数据。对CSS代码中非法的语法她会直接忽略掉。解析CSS的时候会按照如下顺序来定义优先级:浏览器默认设置,用户设置,外链样式,内联样式,html中的style。

3,构建出DOM树,并且计算出样式数据后,下一步就是构建一个 渲染树(rendering tree)。渲染树和DOM树有点像,但是是有区别的。DOM树完全和html标签一一对应,但是渲染树会忽略掉不需要渲染的元素,比如head、display:none的元素等。而且一大段文本中的每一个行在渲染树中都是独立的一个节点。渲染树中的每一个节点都存储有对应的css属性。

4,一旦渲染树创建好了,浏览器就可以根据渲染树直接把页面绘制到屏幕上。一个渲染过程的例子
例如有下面这样一段HTML代码:

每个页面至少在初始化的时候会有一次重排操作。任何对渲染树的修改都有可能会导致下面两种操作:
1,重排就是渲染树的一部分必须要更新 并且节点的尺寸发生了变化。这就会触发重排操作。
2,重绘部分节点需要更新,但是没有改变他的集合形状,比如改变了背景颜色,这就会触发重绘。什么情况下会触发重绘或重排
下面任何操作都会触发重绘或者重排:增加或删除DOM节点设置 display: none;(重排并重绘) 或者 visibility: hidden(只有重排)移动页面中的元素增加或者修改样式用户 改变窗口大小滚动页面等

看一个例子

Paste_Image.png

有些重绘操作会比其他操作昂贵很多。比如你把一个body的子元素做了修改,不一定会导致大量的其他节点更新,但是你把一个元素移动到页面顶部去,可能就会导致全部其他节点进行重排操作,这个代价就非常昂贵。聪明的浏览器
因为渲染树的改变导致的重绘或重排操作都可能代价很高,浏览器会对这个改动做很多优化。一个策略就是不要立即做操作,而是批量进行。比如把你的脚本对DOM的修改放入一个队列,在队列所有操作结束后只需要进行一次绘制即可。但是有的时候脚本可能会导致浏览器的批量优化无法进行,可能在清空队列之前就需要重新绘制(绘制意思是重绘或者重排)页面。比如你通过脚本获取这些样式:offsetTop, offsetLeft, offsetWidth, offsetHeightscrollTop/Left/Width/HeightclientTop/Left/Width/HeightgetComputedStyle(), or currentStyle in IE因为浏览器必须给你最新的值,所以当你进行这些取值操作的时候会立刻触发一次页面的绘制。这样本来可以批量修改样式然后一次性绘制的方法就无法使用了。减少重绘和重排
1,不要一个一个地单独修改属性,最好通过一个classname来定义这些修改//

 badvar left = 10,   
 top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px"; // better el.className += " theclassname";

2,把对节点的大量修改操作放在页面之外用 documentFragment来做修改clone 节点,在clone之后的节点中做修改,然后直接替换掉以前的节点通过 display: none 来隐藏节点(直接导致一次重排和重绘),做大量的修改,然后显示节点(又一次重排和重绘),总共只会有两次重排。

3,不要频繁获取计算后的样式。如果你需要使用计算后的样式,最好暂存起来而不是直接从DOM上读取。

4,总的来说,总是考虑到渲染树得存在,考虑到你的一次修改会导致多大的绘制操作。比如绝对定位元素的动画就不会影响其他大部分元素。

js和css对浏览器渲染文档的阻塞

网页中引用的外部文件: JavaScritp、CSS 等常常会阻塞浏览器渲染页面。假设在 <head> 中引用的某个 JavaScript 文件由于各种不给力需要2秒来加载,那么浏览器渲染页面的过程就会被阻塞2秒,直到该JS文件下载并执行完后才继续。前端性能调优时必须排除任何潜在的渲染阻塞点,让浏览器在最短时间内渲染出整体页面。

js为什么会阻塞

<!doctype html>
<html>
  <head>
    <script type="text/javascript" src="page.js"></script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

上述代码中,当浏览器解析 script 标签时,由于浏览器并不知道 page.js 将会对页面做什么改变,所以浏览器需要停止渲染,下载并执行 page.js 后再继续渲染后面的内容。如果 page.js 的下载过程中出现任何延迟,也将影响整个页面的渲染。

Inline JavaScript

如果页面的初始渲染的确依赖于page.js,我们可以考虑使用内联JavaScript。

<!doctype html>
<html>
  <head>
    <script type="text/javascript">
    /* page.js的内容 */
    </script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

推迟加载

如果页面的初始渲染并不依赖于page.js,我们可以考虑推迟加载page.js,让其在页面初始内容渲染完成后再加载。

<!doctype html>
<html>
  <head>
  </head>
  <body>
    <h1>Hello World</h1>
    <script type="text/javascript" src="page.js"></script>
  </body>
</html>

异步加载

HTML5允许我们给 script 标签添加属性: "async" 来告诉浏览器不必停下来等待该脚本执行,什么时候下载完什么时候执行该脚本就可以了。这样的话浏览器会边下载page.js边渲染后面的内容。

<!doctype html>
<html>
  <head>
    <script type="text/javascript" src="page.js" async></script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

然而如果某个JS被其他JS所依赖,那么就不能使用异步加载了。

<!doctype html>
<html>
  <head>
    <script type="text/javascript" src="jquery-1.11.3.min.js" async></script>
    <script type="text/javascript" src="jq-plugin.js" async></script>
  </head>
  <body>
    <h1>Hello World</h1>
  </body>
</html>

由于使用异步加载后,JS不再顺序执行。上例中 jq-plugin.js 依赖于jQuery,如果 jq-plugin.js 先下载完成,此时jQuery还没下载完,那么浏览器就会先执行 jq-plugin.js 导致出错。当然这类问题可以通过引入依赖管理来解决,这是另外一个主题,就不展开讨论了。

CSS阻塞

由于CSS决定了DOM元素的样式、布局,所以浏览器遇到CSS文件时会等待CSS文件加载并解析完后才继续渲染页面。

Inline CSS

我们可以将那些页面首屏渲染需要用到的CSS代码加入Inline CSS。

<!doctype html>
<html>
  <head>
    <style tpe="text/css">
    .blue {
        color: blue;
    }
    </style>
  </head>
  <body>
    <div class="blue">
      Hello, world!
    </div>
  </body>
</html>

推迟加载CSS

对于那些首屏渲染不需要用到的CSS,我们可以依旧使用文件形式并在页面内容渲染完成后再加载。

<!doctype html>
<html>
  <head>
    <style tpe="text/css">
    .blue {
        color: blue;
    }
    </style>
  </head>
  <body>
    <div class="blue">
      Hello, world!
    </div>
    <link href="other.css" rel="stylesheet" />
  </body>
</html>

结论

在页面加载时我们需要让页面内容尽快呈现给用户,页面初始渲染所需要的JS和CSS可以直接在 <head> 标签中以代码形式插入。所有的外部文件引用可以放在页面内容之后,对于JS文件也可以采用异步加载。

实际的优化建议
汇总了一些有用的信息,我建议以下几点:
创建合法的 HTML 和 CSS ,别忘了制定文件编码,Style 应该写在 head 标签中,script 标签应该加载 body 标签结束的位置。
试着简化和优化 CSS 选择器(这个优化点被大多数使用 CSS 预处理器的开发者忽略了)。将嵌套层数控制在最小。以下是 CSS 选择器的性能排行(从最快的开始):

ID选择器:#id

class选择器: .class

标签: div

相邻的兄弟元素:a + i

父元素选择器: ul > li

通配符选择器: *

伪类和伪元素: a:hover
,你应该记住浏览器处理选择器是从右向左的,这也就是为什么最右面的选择器会更快——#id
或.class

; html-script: false ]div * {...} // bad
.list li {...} // bad
.list-item {...} // good
#list .list-item {...} // good

在你的脚本中,尽可能的减少 DOM 的操作。把所有东西都缓存起来,包括属性和对象(如果它可被重复使用)。进行复杂的操作的时候,最好操作一个“离线”的元素(“离线”元素的意思是与 DOM 对象分开、仅存在内存中的元素),然后将这个元素插入到 DOM 中。
如果你使用 jQuery,遵循jQuery 选择器最佳实践
要改变元素的样式,修改“class”属性是最高效的方式之一。你要改变 DOM 树的层次越深,这一条就越高效(这也有助于将表现和逻辑分开)。
尽可能的只对 position 为 absolute 或 fix 的元素做动画。
当滚动时禁用一些复杂的 :hover
动画是一个很好的主意(例如,给 body 标签加一个 no-hover 的 class)关于这个主题的文章

参考

http://www.cnblogs.com/chenlogin/p/5221562.html

http://highfly-s.iteye.com/blog/1908259

http://blog.csdn.net/lihongxun945/article/details/37830667

http://joji.me/zh-cn/blog/web-performance-optimization-remove-blocking-javascript-and-css

上一篇下一篇

猜你喜欢

热点阅读