1、现代浏览器,比如chrome,firefox都支持硬件加速,GPU加速功能,开启后,使用相关CSS属性,3D API,canvas等,都会默认用GPU渲染的方式去绘制图像。
浏览器渲染的过程,网上图片杂且准确性出处都有待考究,关于这个过程和原理,国外有一个最权威的文档资料:链接
(差不多网上的文章都是从这里盗的内容了),浓缩一下流程,可以概括为:
1)可以看到DOM Tree 是由HTML等标签和Script的构建的,然后是解析Scipt标签,这里并行,异步,还是阻塞,可以通过script的async defer来控制,没有加这些默认是会阻塞DOM Tree的构建。
2)解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。
CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element。也就是DOM结点。也就是所谓的Frame。(这里的Rendering Tree和Frame的叫法查资料是分别在WebKit和Gecko内核的两种叫法)
然后,计算每个Frame(也就是每个Element)的位置,这又叫layout或者reflow过程。(layout是Webkit的叫法,reflow是Gecko的叫法)
3)最后通过调用操作系统Native GUI的API绘制。
2、上面是浏览器的过程,这里再重点说绘制像素前的关键的两个概念,回流Reflow(也叫重排),重绘Repaint。
Repaint——屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变。
Reflow——意味着元件的几何尺寸变了,我们需要重新验证并计算Render Tree。是Render Tree的一部分或全部发生了变化。这就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式布局,所以,如果某元件的几何尺寸发生了变化,需要重新布局,也就叫reflow)reflow 会从这个root frame开始递归往下,依次计算所有的结点几何尺寸和位置,在reflow过程中,可能会增加一些frame,比如一个文本字符串必需被包装起来。
Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。在一些高性能的电脑上也许还没什么,但是如果reflow发生在手机上,那么这个过程是非常痛苦和耗电的。(之前强哥就说用了容易引起reflow的方式做动画最后手机表现上很卡)
reflow肯定会引起repaint(注:display:none会触发reflow,而visibility:hidden只会触发repaint,因为没有发现位置变化。)
3、一个可能会引起你惊讶的消息是,当你获取请求DOM的一些值的时候也会立即引起reflow:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop/Left/Width/Height
clientTop/Left/Width/Height
IE中的 getComputedStyle(), 或 currentStyle
到底那些会引起浏览器的这些过程,除了靠经验和想象,也可查:
https://gist.github.com/paulirish/5d52fb081b3570c81e3a
哪些CSS属性会引起reflow还是repaint,会不会走GPU加速,在不同浏览器会不会有差异,这些可查:https://csstriggers.com (神站!)
4、浏览器在每一帧中,都要经过下列动作
所有的渲染对象都有一个layout或reflow方法,每个渲染对象调用需要布局的children的layout方法。
(JavaScript和Style可以看做dom解析和样式表解析并合并render tree的过程)
减少layout(这个应该就是重排回流动作),paint(这个当重新渲染的时候就对应重绘操作),就尽量应该让属性引起的变化在Composite过程完成,也就是组合层,也叫渲染层合并,按照合理的顺序合并图层然后显示到屏幕上。
利用 GPU 加速优先使用渲染层合并属性,避免 layout,paint。
例如一个网页的加载过程:
可以在上面看到,transform这个属性过程是在Composite过程完成,相当于自己提升(创建)了一个图层,最后合并,没有影响之前的图层,不会引起layout和paint了,这样动画效果更加流畅。
打开chrome工具rendering的layer Borders可以看到很多边框和栅格,
黄色边框:有动画 3d 变换的元素,表示放到了一个新的复合层(composited layer)中渲染
蓝色的栅格:这些分块可以看作是比层更低一级的单位,这些区域就是 RenderLayer
打开一个页面,如果该页面的黄色边框很多,那么肯定要查看一下原因了
打开csstrigers网站,查询transform,可以看到
在Blink Chrome浏览器和WebKit火狐浏览器上都是在Composite上完成的。“更改变换不会触发任何几何更改或绘制,这非常好。这意味着该操作可能由合成器线程在GPU的帮助下执行。”
参考:https://aotu.io/notes/2017/04/11/GPU/index.html
5、CSS会不会阻塞DOM解析/DOM渲染/浏览器渲染?
这个问题在实际表现中其实复杂,参考:链接
这位博主也只是列出了几种情况下的表现,总结为:
-
CSS加载不阻塞DOM解析。DOM解析和CSS解析是两个并行的进程。
-
CSS加载会阻塞DOM的渲染。由于Render Tree是依赖于DOM Tree和CSSOM Tree的,所以他必须等待到CSSOM Tree构建完成,也就是CSS资源加载完成(或者CSS资源加载失败)后,才能开始渲染。
-
CSS会阻塞后面js的执行。由于js可能会操作之前的Dom节点和css样式,因此浏览器会维持html中css和js的顺序。因此,样式表会在后面的js执行前先加载执行完毕。所以css会阻塞后面js的执行。
写到这里,就不得不提下浏览器中的一个生命周期事件:DOMContentLoaded
6、关于DOMContentLoaded
首先看看MDN上的定义:
当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。另一个不同的事件 load 应该仅用于检测一个完全加载的页面。 在使用 DOMContentLoaded 更加合适的情况下使用 load 是一个令人难以置信的流行的错误,所以要谨慎。注意:DOMContentLoaded 事件必须等待其所属script之前的样式表加载解析完成才会触发。
看看,一下子就把上面联系起来了,举了一个例子:
如果script前有CSS的link,那么这个事件就会在3秒css解析完才会触发执行。如果link放在之后,就会立即执行打印。
解释:
因为js会阻塞DOM解析,DOMContentLoaded是在DOM解析完成后才触发。因此,当css后面有js的时候,css会阻塞js运行,而js会阻塞DOM解析,从而导致DOMContentLoaded必须等到css以及css后面的js执行完成后,才会触发。(而当css后面没有js的时候,由于css不阻塞DOM的解析,因此DOMContentLoaded不会等待css的加载。)
需要注意的是,现在我们一般把script放在body的最后,如果这块的script含有对DOMContentLoaded事件的监听回调函数,那么就不合理了,因为放在最后的这块一定会被head的CSS link所阻塞。解决方法是把DOMContentLoaded监听回调函数单独放在一个script放在head的所有link CSS之前。或者用新H5 API defer和asyc script解决(未验证)。