一直对于浏览器的渲染机制一知半解,这种一知半解也导致我对WEB性能优化“一知半解”。为什么JS会阻塞页面,CSS不会?究竟什么叫阻塞?浏览器是如何解析html,css,js文件的,其顺序是如何的?这些问题今天我终于在看到一篇优秀的文章后得到解答。下面我会整理一下我理解的东西,若有不足之处还请提出或者直接参考原作者文章(放在文末)
面试中我们一般会被问到两类问题,问题1:为什么样式要放在head里,而script[src]标签放在最底部(最靠近body结束标签的地方)?面试者往往会回答:JS会阻塞页面,CSS不会。 (然而为什么?)问题2:如何提升transform动画的性能?面试者也往往会回答:将2D变换属性换成3D变换属性,如将translate换成translate3d。使用3D可以告诉浏览器开启GPU渲染,提升渲染性能。(为什么开启了GPU渲染就可以提升性能呢?)
上面括号中的问题,都要从浏览器的渲染机制究起...
这两类问题对应两个阶段,问题1要从浏览器第一次渲染页面说起,问题2要从页面变更后浏览器的渲染讲起。
关键渲染路径
浏览器从接收到页面开始到页面显示,这整个过程中的所有步骤,称为关键渲染路径。用户看到页面实际上可以分为两个阶段:页面内容加载完成和页面资源完成,分别对应于DOMContentLoaded和Load。从DevTool-Network面板上看,如下图:
整个渲染路径包括以下几个步骤:
1.解析HTML,生成DOM树
2.解析CSS,生成CSSOM
3.将DOM和CSSOM合并,生成渲染树(render tree)
4.计算渲染树的布局
5.将布局渲染到屏幕上
从上面看出,我们并没有提到脚本JS的处理,并不是脚本处理不在关键渲染路径中,而是因为JS的处理会对1、2产生影响,我们需要单独去解释。
DOM生成
浏览器在获取HTML后,解析HTML代码,将HTML的元素关系转换成一个数据结构,就是我们所熟知的DOM(Document Object Model)。
在解析HTML过程中,会碰到几类特殊的节点需要特殊的处理:
1.style、link元素以及具有内联样式的元素:交给“CSSOM生成”
2.script(无论是否外链)元素:见“Script标签的处理”
P.S. 思考:碰到img、video、audio等资源性标签怎么办?
解析完HTML,单纯使用DOM,浏览器并不知道如何渲染这棵树,DOM只是存储了元素的关系,并没有任何渲染信息,如宽高、颜色、背景、定位等。存储这些信息,就需要CSSOM了。扒一张Chrome内部文章的例子来总结:
CSSOM生成
上面讲到的,在解析HTML的过程中,会碰到style、link标签,以及具有内联样式的元素,这时浏览器会将解析DOM换成解析CSSOM(所以CSS也是会阻塞HTML的解析!),CSSOM和DOM是两个不同的数据结构。
对于style和内联样式,会之间根据样式声明解析成CSSOM;
然而对于link,即外联样式,浏览器会先发请求去请求CSS文件,待请求成功,获取外联样式后,浏览器便会解析该外联样式,生成相应的CSSOM。
由于CSSOM负责存储渲染信息,浏览器就必须保证在渲染树合成之前,CSSOM是完备的,意思就是所有CSS(内联,外联)都已经下载完,也解析完。只有CSSOM和DOM的解析完全结束,浏览器才会进入下一步的渲染--渲染树,这就是传说中的CSS阻塞渲染。
CSS阻塞渲染意味着,在CSSOM完备前,页面将一直处理白屏状态,这就是为什么样式放在head中,仅仅是为了更快的解析CSS,保证更快的首次渲染。
需要注意的是,即便你没有给页面任何的样式声明,CSSOM依然会生成,默认生成的CSSOM自带浏览器默认样式(default styles)。
样式解析生成的CSSOM便含有渲染信息,这些信息会与DOM一起,生成渲染树Render-Tree。最后,一样附上Chrome官方的事例来个总结:
Script标签的处理
因为JS可以操作DOM来改变DOM结构,可以操作CSS来改变CSSOM,这就导致了浏览器在解析HTML时,一旦碰到script,就要立即停止HTML解析(CSS不会),执行JS,再返还控制权。
JS执行请不仅停止了HTML解析,还必须等到前面元素的CSSOM解析完成。就是说,但碰到script元素时,发现该元素前面的CSS还没解析完,就要等待其解析完,方可执行JS。
JS阻塞了HTML的解析,也阻塞了其后的CSS解析,整个解析进程必须等待JS的执行完成才能够继续,这就是所谓的JS阻塞页面。一个script标签,推迟了DOM的生成、CSSOM的生成以及之后的所有渲染过程,从性能角度上讲,将script放在页面底部,也就合情合理了。(作者原话,总结得太准确了!)
渲染树
DOM和CSSOM,一个存储了节点信息,一个存储了节点渲染信息,都不能直接用来渲染。因此浏览器先将其合成渲染树,这棵树就包含了页面所有可见元素和他们的渲染信息。仍以上述同样的例子:
生成渲染树,浏览器做了这些工作:
1.从DOM的根节点(html)开始,遍历每个可视节点:script、link、meta都属于不可视节点,另外,display: none的节点也属于不可视节点
2.从CSSOM中搜索可视节点的样式
3.计算这些样式,将计算值应用到可视节点上。
渲染树生成后,还是没有办法渲染到屏幕上,渲染到屏幕需要得到各个节点的位置信息,这就需要布局(Layout)的处理了。
布局
渲染树生成后,浏览器便可以根据渲染树中的样式信息,结合设备的屏幕信息,计算每个元素的位置和尺寸。
渲染
得到了渲染树及其节点的布局信息,浏览器便可以将最终的页面渲染到屏幕。
整个关键渲染路径就包括了以上的步骤,每个步骤的快慢都影响这网页的性能,或者说网站的性能。因此,谈到首屏或者首渲染的性能优化,就要从关键渲染路径入手,每一步都有或多或少的可优化点。
当我们的页面首渲后,会有很多页面的交互,例如:动画、用户点击、滚屏、hover、click,所有的交互都会引发浏览器新的渲染操作,这些操作之间影响着用户交互性能,Chrome官网里称作渲染性能。
渲染流程
首先,我们要先了解一个概念:设备刷新率。
设备刷新率是设备屏幕渲染的频率,通俗一点就是,把屏幕当作墙,设备刷新率就是多久重新粉刷一次墙面,对应到设备上就是多久刷新一次屏幕。基本我们平常接触的设备,如手机、电脑,它们的默认刷新频率都是60FPS,也就是屏幕在1s内渲染60次,约16.7ms渲染一次屏幕。
这就意味着,我们的浏览器最佳的渲染性能就是所有操作能在一帧16.7ms内完成,能否做到一帧内完成直接决定着渲染性,影响用户交互,这就要求我们需要了解浏览器的一个渲染过程,包括了哪些操作。
完整的渲染流程由以下几步组成:
- JS:渲染引擎会等待所有的JS操作完成,收集JS对DOM和CSSOM的操作结果
- Style:样式计算,计算交互引起的样式变更,并应用到相应的节点上
- Layout:布局,根据新的Style,计算出新的节点位置和尺寸信息
- Paint:渲染,计算最终的渲染信息(与上述的关键渲染路径-渲染好像不同,其实是一样的,只是上面直接跳到了渲染到屏幕这一步),在实际的渲染中,浏览器会尽可能地在多个层上去渲染,这个层类似PS里的图层概念
- Composite:合成,将每个渲染层合并,生成最终的一层渲染画面。Paint阶段,每个层独立渲染,并不关心与其他层之间的关系,Composite就需要将这些层以正确的关系合成,有点像PS的导出PNG。合成发生在GPU上。
完整的渲染流程就这样,但是,并不是所有的交互都要走一遍这个流程,事实上,从性能角度讲,我们更希望的是每个交互都能省它几个步骤。确实,也是做得到的,除了这种走遍天下的渲染流程,“省步骤”的渲染流程有以下两种:
1.缺layout
layout是计算节点的位置和尺寸信息,如果我们修改的样式不包括修改布局,浏览器就会省略这个步骤。比如,只是修改color。layout是很耗渲染性能的,所以从性能优化角度讲,能避免Layout就避免。除了修改布局属性会触发Layout外,很多获取布局信息的JS操作也会引发Layout,如offsetHeight,getComputedStyle。
2.缺Layout和Paint
缺Layout上面已经解释过了,而避免Paint呢,就是让浏览器将渲染直接交给合成,目前transform和opacity两类样式属性是可以直接跳过Paint的。至于将translate2D变3D,实际上是触发了层提升,使得相应的元素渲染可以在独立层上与其他层并行处理,间接提升了渲染性能。至于别人说的触发GPU加速,也只是因为被新建了一个层。
似乎提升层来提升性能是个很不错的玩法,但是,你的硬件不是无限的,每多一个层,就会多一份内存,因此,控制层数,也是很重要的性能提升。
除了将2D变3D可以达到层的提升,现代浏览器也加上了一个新的样式属性,来“预先”告知浏览器,提升层来处理相应元素的渲染,这个属性名字也是很不错的:will-change
使用该属性,你不必translate3d,只需要:
.my-class {
will-change: transform;
}
当然,兼容性是个问题,自行caniuse。
正因为transform和opacity可以跳过Paint,并且可以在某种形式下告知浏览器优先以GPU来渲染,才有了现代CSS动画推崇优先使用transform,避免使用position、height等属性的变更来处理动画。一些流行的动画库,如iScroll、Swiper.js等,都是使用transform来处理位置偏移,而非top、left等,就是因为性能更高。
问题
1.HTML解析时,碰到img、video、audio等资源性标签怎么办?
待回答
2.CSS和JS的请求是在什么时候?
答:碰到link标签时会去请求CSS,碰到script标签时会去请求JS
3.CSS真的不阻塞HTML解析吗?
答:不是的,在解析HTML的过程中,会碰到style、link标签,以及具有内联样式的元素,这时浏览器会将解析DOM换成解析CSSOM,所以CSS也是会阻塞HTML的解析。
参考:
https://www.jianshu.com/p/b22ff1771225