https://sylvanassun.github.io/2017/10/03/2017-10-03-BrowserCriticalRenderingPath/
http://www.ruanyifeng.com/blog/2015/09/web-page-performance-in-depth.html
浏览器渲染过程,分为以下几步:
1:构建dom树
2:构建css树,
3:合并dom树以及css树,构建渲染树,在这个过程中,会过滤掉不可见节点。例如使用css设置了display:none属性的节点,但是要注意一点,visibility: hidden这个属性不是不可见属性,他的意思是隐藏dom节点,但是仍然占据布局空间
4:生成布局,遍历每一个节点,计算出dom元素的位置(重排)
5:将节点绘制到屏幕上。(重绘)
如果浏览器渲染一个页面,首先就是要构建dom树以及css树,如果页面非常复杂,这两步就会给页面加载速度带来影响。
dom树优化
所以对于dom树的优化,我们在写页面时,应该应该尽可能的减少dom元素
css树优化
对于css树的优化,可以让css在不同的情况下加载不同的css资源,而不是一次性全部加载,这样就可以减少css树的构建时间。这一步的优化可以使用css的媒体查询来实现。
媒体查询是由媒体类型以及0个或多个检查媒体特定类型的表达式组成。具体如下:
注意:使用媒体查询可以让css文件不会再加载时阻塞渲染,但是浏览器依然会将文件下载下来的。
优化JavaScript脚本
当html解析器遇到一个script标签时,会暂停构建dom树,将控制权交给JavaScript引擎,然后由JavaScript引擎去执行脚本,这会严重阻塞dom树的构建过程。而且在执行JavaScript脚本的过程中,如果操作了css,而这个css文件还没有下载和执行,那么脚本执行以及dom树构建都会被阻塞,等待浏览器下载css文件以及构建完css树后,才会接着执行JavaScript脚本。所以JavaScript脚本的位置放的位置非常重要,不然会严重阻塞浏览器渲染
在执行JavaScript脚本时,如果操作了dom元素,而这个元素还没有构建好,那么就会报错。所以就讲script标签放到页面的最下方,或者使用window.onload()和JQuery中的$(function(){})这两个方法。不过这两个方法还是有区别的,onload是在页面渲染完成以后,执行JavaScript脚本,而jq的方法是在dom树构建完成后就开始执行了。
给script标签加上async属性,可以使这个script脚本异步加载,并不会阻塞dom树的构建,但当这个脚本异步下载完成后,会直接执行这个脚本
总结:
网页请求到渲染的整个过程
浏览器通过url请求到html页面,然后html解析器开始构建dom树,构建的过程中,如果遇到link标签,那么浏览器就会请求这个css文件,然后开始构建css树,如果遇到script标签,那么浏览器就会请求这个脚本文件,html解析器会将控制器权交给JavaScript引擎,执行这个脚本。当dom树与css树都构建出来后,就会合并出渲染树,接着便是计算位置,绘制到屏幕上。
在这个过程中,
优化css树的方法使用媒体查询,根据不同的媒体状态,加载不同的css资源。
优化JavaScript脚本的方法就是异步,使用async这个属性
描述关键渲染路径性能的词汇:
关键资源:指的是html页面、css文件以及JavaScript脚本
关键路径长度:获取资源的往返次数或者总时间
关键字节:所有资源文件的总和
关键路径渲染:浏览器接收到HTML文件、css文件、JavaScript脚本并对其进行解析以及转换成像素的整个渲染过程称为关键路径渲染
其他的优化方案:
1:加载部分HTML,其他的HTML通过ajax请求返回
2:通过对外部资源进行压缩来减少关键路径长度以及关键字节
在进行文件压缩之前,可以先进行一次冗余压缩,比如注释、空格符换换行符
3:http缓存
可以将一些静态的资源缓存起来,然后每次请求的时候带一个标示,后台通过这个标示进行判断资源是否有改变,如果改变则返回新的资源文件,如果没改变,则返回304,复用缓存。
4:资源预加载
资源预加载是推测出用户接下来有可能访问哪些资源,然后对这些资源进行预加载
预加载的方式有以下几种
1:提前进行dns解析,以便之后快速访问另一个主机名 // rel="dns-prefetch" href="other.hostname.com"
2:提前下载好资源文件, 使用的属性不同,优先级不同。prefetch 优先级最低 subresource 优先级最高
3:提前将页面渲染出来 并隐藏 // link rel="prerender" href="//domain.com/next_page.html
网页在生成的时候会渲染一次,而在用户浏览的过程中,还会不断的重新渲染,重新渲染包括两种情况,重新排版布局以及重新绘制到屏幕上。重新排版就是重排,重新绘制就是重绘。所以这个时候就要优化重排以及重绘的时间。
重排一定会重绘,而重绘不一定重排
影响重排的情况如下:
1:修改样式表,例如调整某个dom元素的宽度、高度。。
2:修改dom元素。例如添加、删除、移动dom元素
3:用户事件,例如滚动、输入框、改变窗口大小
总结:只改变了dom元素的‘位置’,就一定会重排
影响重绘的情况就是改变一些元素的颜色。
对于重新渲染的优化就是减少重排以及重绘的次数。如今浏览器已经很智能,会尽量把所有的改变放到一个队列中,然后一次性执行。而我们所要做的优化就是尽量避免浏览器被我们“强制”重排或重绘。
对dom元素的读写操作分开
我们应当知道,只读dom元素的属性或者样式并不会造成重新渲染,而我们先改变dom的元素的样式,再去读dom元素的属性就一定会造成重新渲染。所以我们应该将多个写操作放在一起,将多个读操作放在一起,尽量避免多词重新渲染。
div.style.color='blue'
div.style.marginTop='30px'
以上操作浏览器只会渲染一次
div.style.color='blue'
var margin=parseInt(div.style.marginTop)
div.style.marginTop=(margin+10)+'px'
而这段代码就会使浏览器渲染两次。先做了写操作,然后对dom元素进行读操作的时候就会造成dom元素重新排版,也就是重新渲染。然后获取到值后又进行了写操作,所以会进行两次重新渲染。
改成如下:
var margin=parseInt(div.style.marginTop)
div.style.color='blue'
div.style.marginTop=(margin+10)+'px'
这段代码先进行了读操作,接着进行了两次写操作,所以只会重新渲染一次。
其他提高性能的方法:
1:如果某个样式是重排得到的,建议进行缓存,这样便不用每次获取值都造成重新渲染
2:对dom如果要进行多次读写操作,建议使用虚拟dom。
方法:将一个dom元素进行clone,然后操作完毕后可以插入或替换原节点。
如果进行多次读写操作,有可能造成多次重新渲染,而使用虚拟dom的方式只会在替换或者插入时进行一次重排
3:absolute或fixed的元素重绘的开销比较小,因为不用考虑对别的dom元素的影响
4:display设置为可见时,会进行重绘和重排,因为display为none时,并不占据空间。visibility 从hidden变为可见时,只进行重绘,并不会进行重排。因为visibility 的值为隐藏时,这个dom元素依然占据空间
5:使用 window.requestAnimationFrame()、window.requestIdleCallback() 这两个方法调节重新渲染
浏览器渲染与优化整个流程总结:
网页请求到渲染整个流程:
浏览器通过url请求到html页面,然后html解析器开始构建dom树,构建的过程中,如果遇到link标签,那么浏览器就会请求这个css文件,然后开始构建css树,如果遇到script标签,那么浏览器就会请求这个脚本文件,html解析器会将控制器权交给JavaScript引擎,执行这个脚本。当dom树与css树都构建出来后,就会合并出渲染树,接着便是计算位置,绘制到屏幕上。
整个渲染流程分为五步:
1:构建css树
优化方式:使用媒体查询,将不同的css拆开。让css在不同的情况下,加载不同的css资源。比如:media=print,可以使打印的css资源在用户浏览器的过程中不会阻塞渲染
2:构建dom树
优化方式:布局时尽可能少的dom元素写成所需的UI效果
3:dom树与css树合并,构建渲染树
4:根据渲染树生成布局(如果用户在浏览过程中,改变了dom元素,就会重新布局,也就是重排)
5:布局生成后,就会将dom节点绘制到屏幕上(重新布局后,就是重绘)
第四部和第五步的优化:
1:对dom元素的读写分开操作,因为读取某个dom元素的状态时,并不会造成重绘或者重排,但是如果先使用了写操作,改变了某个dom的属性,那么这个时候在进行读操作时一定会进行重排。
2:对dom节点进行多步操作时,使用虚拟的dom节点。然后在全部操作完成后,将dom节点插入或替换到dom文档中。因为使用虚拟dom节点,只有在插入或者替换的时候,才会造成浏览器重排或者重绘。
3:absolute或fixed的元素重排时开销比较小,因为这种布局方式不用考虑对文档流的影响
对JavaScript的优化:首次加载页面时,JavaScript脚本的执行会影响渲染树的构建,所以可以给不依赖dom节点的JavaScript脚本加上async属性。
其他的优化方式:
关键资源压缩、http缓存