前面有讲到当用户在浏览器输入url以后,通过一系列的过程,会最终向服务器请求到文档数据,文档数据请求到以后,浏览器会将这些数据传给浏览器渲染引擎,渲染引擎开始正式工做了。javascript
首先浏览器接收到html文档,就会把HTML在内存中转换成DOM树,HTML中的每一个tag都是DOM树中的1个节点,根节点就是咱们经常使用的document对象。DOM树里包含了全部HTML标签,包括display:none隐藏,还有用JS动态添加的元素等。在转换的过程当中若是发现某个节点(node)上引用了CSS或者 image,就会再次向服务器请求css或image,而后继续执行构建dom树的转换,而不须要等待请求的返回,当请求的css文件返回后,就会开始解析css style,浏览器把全部样式(用户定义的CSS和用户代理)解析成样式结构体,在解析的过程当中会去掉浏览器不能识别的样式,好比IE会去掉-moz开头的样式,而FF会去掉_开头的样式。css
DOM Tree 和样式结构体组合后构建render tree,也就是渲染树。渲染树和dom树有很大的区别,render tree中每一个NODE都有本身的style,并且 render tree不包含隐藏的节点 (好比display:none的节点,还有head节点),由于这些节点不会用于呈现,并且不会影响呈现的,因此就不会包含到 render tree中。注意 visibility:hidden隐藏的元素仍是会包含到 render tree中的,由于visibility:hidden 会影响布局(layout),会占有空间。根据CSS2的标准,render tree中的每一个节点都称为Box (Box dimensions),理解页面元素为一个具备填充、边距、边框和位置的盒子。一旦render tree构建完毕后,浏览器就能够根据render tree来绘制页面了。html
注意:因为浏览器的流布局,对渲染树的计算一般只须要遍历一次就能够完成。但 table及其内部元素除外,它可能须要屡次计算才能肯定好其在渲染树中节点的属性,一般要花3倍于同等元素的时间。这也是为何咱们要避免使用 table作布局的一个缘由。java
在浏览器进行加载时,实际上是并行加载全部资源。对于css和图片等资源,浏览器加载是异步的,并不会影响到后续的加载、html解析和后续渲染。node
css阻塞渲染
由上面过程能够看到,页面布局是在渲染树构建好以后发生的,而渲染树依赖css样式结构体,因此CSS 被视为阻塞渲染的资源(但不阻塞html的解析,不会阻塞dom树的构建),这意味着浏览器将不会渲染任何已处理的内容,直至 CSSOM 构建完毕。jquery
由于css会阻塞渲染,因此咱们应该尽早的尽快地下载到客户端,以便缩短首次渲染的时间。平时在开发的时候,应注意如下几点:chrome
同时,还有如下优化点:浏览器
1、媒体查询缓存
经过使用媒体查询,咱们能够根据特定的需求(好比显示或打印),也能够根据动态状况(好比屏幕方向变化、尺寸调整事件等)定制外观,服务器
<link href="style.css" rel="stylesheet"> <link href="print.css" rel="stylesheet" media="print"> <link href="other.css" rel="stylesheet" media="(min-width: 40em)">
看上面的代码,
第一行,这样的普通声明,会阻塞渲染
第二行,这个声明,只在打印网页时应用,所以网页在浏览器中加载时,不会阻塞渲染。
第三行,提供了由浏览器执行的“媒体查询”,只有符合条件时,样式表会生效,浏览器才会阻塞渲染,直至样式表下载并处理完毕。
2、preload
<link rel="preload" href="index_print.css" as="style" onload="this.rel='stylesheet'">
preload是resoure hint规范中定义的一个功能,顾名思义预加载,将rel改成preload后,至关于加了一个标志位,浏览器解析的时候会提早创建链接或加载资源,作到尽早并行下载,而后在onload事件响应后将link的rel属性改成stylesheet便可进行解析。
IE chrome firefox三者的差别
3、动态添加link
var style = document.createElement('link'); style.rel = 'stylesheet'; style.href = 'index.css'; document.head.appendChild(style);
js动态添加DOM元素link,不会阻塞渲染。
loadCSS.js,CSS preload polyfill第三方库,原理同上
4、代码简练
js阻塞
js可能会操做html,css,因为浏览器不了解脚本计划在页面上执行什么操做,它会做最坏的假设并阻止解析器,也就是以前讲过浏览器的GUI线程与js引擎线程是互斥的。因此,js会阻塞渲染
浏览器对于js脚本文件的加载,则会致使html解析和渲染中止,直至js脚本加载并执行完毕才继续,可是对于后续的非js资源加载并不会中止,浏览器会对后续资源进行预加载。而资源加载是属于另外单独的线程,因此js加载并不会影响其余非js资源的加载,是浏览器的机制。
总的来讲就是如下几点:
当CSS后面跟着嵌入的JS的时候,该CSS就会出现阻塞后面资源下载的状况,由于浏览器会维持html中css和js的顺序,样式表必须在嵌入的JS执行前先加载、解析完。而嵌入的JS会阻塞后面的资源加载,因此就会出现CSS阻塞下载的状况。
例以下面这段代码,看浏览器是如何一步步将界面绘制出来
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <meta name="author" content="Reddy.Huang, i@0u0b.com"/> <title>浏览器渲染</title> <link href="./css/main.css" rel="stylesheet"> </head> <body> <div class="wrap"> <div class="left"> </div> <div class="middle"> <div class="line"> </div> </div> <div class="right"> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> </div> </div> <script src="./js/3.js"></script> </body> </html>
经过浏览器的工具上的能够很清楚的看到界面的渲染过程,也能够很清楚的看到请求加载资源的时候,不会对html解析形成影响,但若是资源加载过慢,会致使渲染阻塞,经过此图能够很好的理解浏览器的渲染机制
若是我把js放在css以后,以下代码:
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <meta name="author" content="Reddy.Huang, i@0u0b.com"/> <title>浏览器渲染</title> <link href="./css/main.css" rel="stylesheet"> <script src="./js/3.js"></script> </head> <body> <div class="wrap"> <div class="left"> </div> <div class="middle"> <div class="line"> </div> </div> <div class="right"> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> <p>fgdgg</p> </div> </div> </body> </html>
再次查看浏览器的渲染过程:
图中能够明显的看出,首先浏览器开始解析html,而后再解析的过程当中遇到css,开始加载css资源,遇到js开始加载js资源,当css加载完成后,开始解析css,js加载完成后,则开始解析js,此时解析html生成dom树会中止,直到js解析完成以后,才再次开始解析html,从新计算样式,布局,生成渲染树,最终才是界面绘制,因此在开发的时候不要将js文件写在头部,这样会影响界面的绘制,致使界面出现空白
重绘
当render tree中的一些元素须要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,好比background-color。则就叫称为重绘。
回流
当render tree中的一部分(或所有)由于元素的规模尺寸,布局,隐藏等改变而须要从新构建。这就称为回流(reflow)。
每一个页面至少须要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并从新构造这部分渲染树,完成回流后,浏览器会从新绘制受影响的部分到屏幕中,该过程成为重绘。
回流必然会形成重绘,重绘不会形成回流。
回流什么时候发生:
当页面布局和几何属性改变时就须要回流。下述状况会发生浏览器回流:
一、添加或者删除可见的DOM元素;
二、元素位置改变;
三、元素尺寸改变——边距、填充、边框、宽度和高度
四、内容改变——好比文本改变或者图片大小改变而引发的计算值宽度和高度改变;
五、页面渲染初始化;
六、浏览器窗口尺寸改变——resize事件发生时;
回流比重绘的代价要更高,回流的花销跟render tree有多少节点须要从新构建有关系,假设你直接操做body,好比在body最前面插入1个元素,会致使整个render tree回流,这样代价固然会比较高,但若是是指body后面插入1个元素,则不会影响前面元素的回流。
若是每句JS操做都去回流重绘的话,浏览器可能就会受不了。因此不少浏览器都会优化这些操做,浏览器会维护1个队列,把全部会引发回流、重绘的操做放入这个队列,等队列中的操做到了必定的数量或者到了必定的时间间隔,浏览器就会flush队列,进行一个批处理。这样就会让屡次的回流、重绘变成一次回流重绘。
虽然有了浏览器的优化,但有时候咱们写的一些代码可能会强制浏览器提早flush队列,这样浏览器的优化可能就起不到做用了。当你请求向浏览器请求一些 style信息的时候,就会让浏览器flush队列,好比:
当请求上面的一些属性的时候,浏览器为了给你最精确的值,须要flush队列,由于队列中可能会有影响到这些值的操做。即便你获取元素的布局和样式信息跟最近发生或改变的布局信息无关,浏览器都会强行刷新渲染队列。
由于回流的开销很大,因此咱们在写代码的时候,有不少须要注意的地方:
// 很差的写法 var left = 1; var top = 1; el.style.left = left + "px"; el.style.top = top + "px"; // 比较好的写法 el.className += " className1"; // 比较好的写法 el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
a、使用documentFragment或div等元素进行缓存操做,这个主要用于添加元素的时候,你们应该都用过,就是先把全部要添加到元素添加到1个div(这个div也是新加的),最后才把这个div append到body中。
b、先display:none 隐藏元素,而后对该元素进行全部的操做,最后再显示该元素。因对display:none的元素进行操做不会引发回流、重绘。因此只要操做只会有2次回流。
// 别这样写 for(循环) { elel.style.left = el.offsetLeft + 5 + "px"; elel.style.top = el.offsetTop + 5 + "px"; } // 这样写好点 var left = el.offsetLeft,top = el.offsetTop,s = el.style; for(循环) { left += 10; top += 10; s.left = left + "px"; s.top = top + "px"; }
// block1是position:absolute 定位的元素,它移动会影响到它父元素下的全部子元素。 // 由于在它移动过程当中,全部子元素须要判断block1的z-index是否在本身的上面, // 若是是在本身的上面,则须要重绘,这里不会引发回流 $("#block1").animate({left:50}); // block2是相对定位的元素,这个影响的元素与block1同样,可是由于block2非绝对定位 // 并且改变的是marginLeft属性,因此这里每次改变不但会影响重绘, // 还会引发父元素及其下元素的回流 $("#block2").animate({marginLeft:50});
参考文章:
https://www.cnblogs.com/kevin...
https://blog.csdn.net/allenli...
https://www.css88.com/archive...