一、css渲染原理(Cascading Style Sheets)
在介绍css渲染原理之前,咱们简明扼要介绍一下页面的加载过程和浏览器渲染过程,有助于更好了解后续css渲染原理。
1.1页面的加载过程和浏览器渲染过程
1.1.1页面加载过程
要点如下:

浏览器依据DNS服务器失去域名的IP地址
向这个IP的机器发送HTTP申请
服务器收到、解决并返回HTTP申请
浏览器失去返回内容(其实就是一堆HTML格局的字符串,因为只有HTML格局浏览器能力正确解析,这是W3C规范的要求。接下来就是浏览器的渲染过程。)
1.1.2 浏览器渲染过程
第一步:html通过HTML parser解析为DOM tree;(浏览器会把HTML构造字符串解析转换DOM树形构造。)
第二步:css依据css规定通过css解析器解析为 style Rules(CSSOM tree);
第三步:两棵树通过attachment联合Render Tree(造成一颗大树,此时它还是一颗迷茫的树,不晓得节点的内容和地位)
第四步:render tree (渲染树)通过Layout计算DOM的地位以及款式;
第五步: 讲计算好的页面paint画进去;
第六步:显示到浏览器上;
流程图:

简而言之,浏览器就是解析DOM生成DOM Tree,联合CSS生成CSS Tree,最终组成render tree,再渲染页面。(构建DOM->构建CSSOM->构建渲染树->布局->绘制)
1.1.3影响DOM树构建的因素(js和css)
先做个总结,而后再进行具体的剖析:
CSS不会阻塞DOM的解析,然而会影响JAVAScript的运行,javaSscript会阻止DOM树的解析,最终css(CSSOM)会影响DOM树的渲染,也能够说最终会影响渲染树的生成。
接下来咱们先看javascript对DOM树构建和渲染是如何造成影响的,分成三种类型来解说:
1、JavaScript脚本再html页面中
<html>
<body>
<div>1</div>
<script>
let div1 = document.getElementsByTagName('div')[0]
div1.innerText = 'time.geekbang'
</script>
<div>test</div>
</body>
</html>

我在两段div两头插入了一段JavaScript脚本,这段脚本的解析过程就有点不一样了。
通过后面DOM生成流程剖析,咱们曾经晓得当解析到script脚本标签时,其DOM树结构如下所示:

这时候HTML解析器暂停工作,javascript引擎染指,并执行script标签中的这段脚本,因为这段javascript脚本批改了DOM中第一个div中的内容,所以执行这段脚本之后,div节点内容曾经批改为time.geekbang了。脚本执行实现之后,HTML解析器回复解析过程,持续解析后续的内容,直至生成最终的DOM。
以上过程应该还是比拟好了解的,不过除了页面中间接内嵌JavaScript脚本之外,咱们还通常须要在页面中引入JAVAScript文件,这个解析过程就略微简单了些,如上面案例:
2、html页面中引入javaScript文件

//foo.js
let div1 = document.getElementsByTagName('div')[0]
div1.innerText = 'time.geekbang'

<html>
<body>
<div>1</div>
<script type="text/javascript" src='foo.js'></script>
<div>test</div>
</body>
</html>

这段代码的性能还是和后面那段代码是一样的,不过这里我把内嵌JavaScript脚本批改成了通过javaScript文件加载。其整个执行流程还是一样的,执行到JAVAScript标签时,暂停整个DOM的解析,执行javascript代码,不过这里执行javascript时,须要当初在这段代码。这里须要重点关注下载环境,因为javascript文件的下载过程会阻塞DOM解析,而通常下载又是十分耗时的,会受到网络环境、javascript文件大小等因素的影响。
优化机制:
不过谷歌浏览器做了很多优化,其中一个次要的优化就是预解析操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来剖析HTML文件中蕴含的JavaScript、CSS等相干文件,解析到相干文件之后,会开启一个预解析线程,用来剖析HTML文件中蕴含的javascprit、css等相干文件、解析到相干文件之后,预解析线程会提前下载这些文件。前端培训
再回到DOM解析上,咱们晓得引入javascprit线程会阻塞DOM,不过也有一些相干的策略来躲避,比方应用CDN来减速Java
Script文件的家在速度,压缩javascript、CSS等相干文件,解析到相干文件之后,预解析线程会提前下载这些文件。
再回到 DOM 解析上,咱们晓得引入 JavaScript 线程会阻塞 DOM,不过也有一些相干的策略来躲避,比方应用 CDN 来减速 JavaScript 文件的加载,压缩 JavaScript 文件的体积。另外,如果 JavaScript 文件中没有操作 DOM 相干代码,就能够将该 JavaScript 脚本设置为异步加载,通过 async 或 defer 来标记代码,应用形式如下所示:

<script async type="text/javascript" src='foo.js'></script>
或者
<script defer type="text/javascript" src='foo.js'></script>

async和defer区别:
async:脚本并行加载,加载实现之后立刻执行,执行机会不确定,仍有可能阻塞HTML解析,执行机会在load事件派发之前。
defer:脚本并行加载,期待HTML解析实现之后,依照加载程序执行脚本,执行机会DOMContentLoaded事件派发之前。
3、html页面中有css款式
案例:

//theme.css
div {color:blue}

<html>

<head>    <style src='theme.css'></style></head>

<body>

<div>1</div><script>    let div1 = document.getElementsByTagName('div')[0]    div1.innerText = 'time.geekbang' // 须要 DOM    div1.style.color = 'red' // 须要 CSSOM</script><div>test</div>

</body>
</html>

该示例中,JavaScript 代码呈现了 div1.style.color = ‘red’ 的语句,它是用来操纵 CSSOM 的,所以在执行 JavaScript 之前,须要先解析 JavaScript 语句之上所有的CSS 款式。所以如果代码里援用了内部的 CSS 文件,那么在执行 JavaScript 之前,还须要期待内部的 CSS 文件下载实现,并解析生成 CSSOM 对象之后,能力执行 JavaScript 脚本。
而 JavaScript 引擎在解析 JavaScript 之前,是不晓得 JavaScript 是否操纵了 CSSOM的,所以渲染引擎在遇到 JavaScript 脚本时,不论该脚本是否操纵了 CSSOM,都会执行CSS 文件下载,解析操作,再执行 JavaScript 脚本。所以说 JavaScript 脚本是依赖样式表的,这又多了一个阻塞过程。
总结:
通过下面三点的剖析,咱们晓得了 JavaScript 会阻塞 DOM 生成,而款式文件又会阻塞js的执行。
4、接下来咱们再看CSS对DOM树构建和渲染的影响:(假如法)
假如css不影响DOM树的解析,这个时候你加载css的时候,很可能会批改上面DOM节点的款式,
如果css加载不阻塞render树渲染的话,那么当css加载完之后,render树可能又得从新重绘或者回流了,这就造成了一些没有必要的损耗。所以这个假如是不成立得。
所以咱们得出:css加载不会阻塞DOM树的解析,但会阻塞render树的渲染(渲染时需等CSS加载结束)

因而,从下面js和css对DOM树构建的剖析得出,应该把CSS放在文档的头部,尽可能的提前加载CSS;把JS放在文档的尾部,这样JS也不会阻塞页面的渲染。CSS会和JS并行解析,CSS解析也尽可能的不去阻塞JS的执行,从而使页面尽快的渲染实现。
1.3 CSS渲染规定
css渲染规定,是从上到下,从右到左渲染的。这是为什么呢?举个例子:

<div>
<div class="jac">

  <p><span> 111 </span></p>  <p><span> 222 </span></p>  <p><span> 333 </span></p>  <p><span class='yellow'> 444 </span></p>

</div>
</div>

<style>
div > div.jartto p span.yellow {
color: yellow;
}
</style>

咱们依照「从左到右」的形式进行剖析:

先找到所有 div 节点。
在 div 节点内找到所有的子 div,并且是 class = “jac”。
而后再顺次匹配 p span.yellow 等状况。
遇到不匹配的状况,就必须回溯到一开始搜寻的 div 或者 p 节点,而后去搜寻下个节点,反复这样的过程。
综上:这样的搜寻过程对于一个只是匹配很少节点的选择器来说,效率是极低的,因为咱们破费了大量的工夫在回溯匹配不合乎规定的节点。
咱们依照「从右向左」的形式进行剖析:
1.首先就查找到 class=“yellow” 的 span 元素。
2 .接着检测父节点是否为 p 元素,如果不是则进入同级其余节点的遍历,如果是则持续匹配父节点满足 class=“jac” 的 div 容器。
3.这样就又缩小了汇合的元素,只有合乎以后的子规定才会匹配再上一条子规定。
综上所述,咱们能够得出结论:
浏览器 CSS 匹配外围算法的规定是以从右向左形式匹配节点的,这样做是为了缩小有效匹配次数,从而匹配快、性能更优。
1.4 CSS选择器权值(优先级)
示例代码一:
<div >
<p id="box" class="text">Jartto's blog</p>
</div>
<style>
#box{color: red;}
.text{color: yellow;}
</style>

当你晓得 「ID 选择器 > 类选择器 」的时候,答案不言自明。
示例代码二:

<div id="box">
<p class="text">Jartto's blog</p>
</div>
<style>
#box{color: red;}
.text{color: blue;}
</style>

这里就考查到了规定「类选择器 > 继承」,ID 对文本来说是继承过去的属性,所以优先级不如间接作用在元素下面的类选择器。
!important > 行内款式(权重1000) > ID 选择器(权重 100) > 类选择器(权重 10) > 标签(权重1) > 通配符 > 继承 > 浏览器默认属性
权值,代表优先级,权值越大,优先级越高。同种类型的选择器权值雷同,后定义的选择器会笼罩先定义的选择器。注:组合应用,权值会叠加
二、优化策略
对于绝大部分的开发者来说,css的性能就是实现页面布局,制订页面的展现成果。其实css也有许多实现Web性能优化的办法。
咱们都晓得对于网站来说,性能至关重要,CSS作为页面渲染和内容展示的重要环节,影响着用户对整个网站的第一体验。因而,与其相干的性能优化是不容忽视的。
2.1 CSS 书写程序对性能有影响吗?
须要留神的是:浏览器并不是一获取到 CSS 款式就立马开始解析,而是依据 CSS 款式的书写程序将之依照 DOM 树的构造散布渲染款式,而后开始遍历每个树结点的 CSS 款式进行解析,此时的 CSS 款式的遍历程序齐全是依照之前的书写程序。
在解析过程中,一旦浏览器发现某个元素的定位变动影响布局,则须要倒回去从新渲染。
栗子:

width: 150px;
height: 150px;
font-size: 24px;
position: absolute;

当浏览器解析到 position 的时候忽然发现该元素是相对定位元素须要脱离文档流,而之前却是依照一般元素进行解析的,所以不得不从新渲染。
渲染引擎首先解除该元素在文档中所占地位,这就导致了该元素的占位状况产生了变动,其余元素可能会受到它回流的影响而从新排位。
咱们对代码进行调整:

position: absolute;
width: 150px;
height: 150px;
font-size: 24px;

在理论开发过程中,咱们如何能保障本人的书写程序是最优呢?
这里有一个标准,倡议程序大抵如下:

定位属性position display float left top right bottom overflow clear z-index
本身属性width height padding border margin background
文字款式font-family font-size font-style font-weight font-varient color
文本属性text-align vertical-align text-wrap text-transform text-indent text-decoration letter-spacing word-spacing white-space text-overflow
CSS3 中新增属性content box-shadow border-radius transform
总之,咱们须要晓得这个规定就够了,剩下的能够交给一些插件去做,譬如 CSSLint
2.2 有选择地应用选择器
大家都晓得css选择器的匹配是从右向左进行的,这一策略导致不同品种的选择器之间的性能也存在差别。
举个例子:
相比于#markdown-content-h3,显然应用#markdown .content h3时,浏览器生成渲染树(render-tree)所要花费的工夫更多。因为后者须要先找到DOM中的所有h3元素,再过滤掉先人元素不是.content的,最初过滤掉.content的先人不是#markdown的。试想,如果嵌套的层级更多,页面中的元素更多,那么匹配所要花费的工夫代价天然更高。
不过古代浏览器在这一方面做了很多优化,不同选择器的性能差异并不显著,甚至能够说差异甚微。此外不同选择器在不同浏览器中的性能体现8也不齐全对立,在编写CSS的时候无奈兼顾每种浏览器。鉴于这两点起因,咱们在应用选择器时,只须要记住以下几点即可。
1、放弃简略,不要应用嵌套过多过于简单的选择器。例:
/ Bad /
div > div > div > p {color:red;}
/ Good /
p-class{color:red;}

2、通配符和属性选择器效率最低,须要匹配的元素最多,尽量避免应用。
只用通配符设定所有根底的款式,如:* {margin:0; padding:0;}这种形式,代码少,然而性能差,因为渲染的时候,要匹配页面上所有的元素!很多根底款式没有margin和padding的元素,比方div,li等。都被匹配,齐全没必要!
3、不要在id选择器前应用标签名,id选择器自身就能惟一确定一个元素,没必要再在后面加上标签名,这样多此一举,还会升高效率。
如:div #box { color: white; } div 可不加
4、不要为了谋求速度而放弃可读性与可维护性。
2.3 把stylesheets放在HTML页面头部
浏览器所有的stylesheets加载实现之后,才会开始渲染整个页面。在这之前,浏览器不会渲染页面的任何内容,页面会始终出现空白。(因为要把stylesheets放在头部的起因)
如果放在HTML页面底部,页面渲染就不仅仅切实等在stylesheets的加载,还要期待html内容的加载实现,这样导致用户看到页面的工夫会更晚。
2.4 缩小应用低廉的属性
在浏览器绘制屏幕时,所有须要浏览器进行操作或计算的属性相对而言都须要破费更大的代价。当页面产生重绘时,它们会升高浏览器的渲染性能。所以在编写CSS时,咱们应该尽量减少应用低廉属性,如box-shadow/border-radius/filter/透明度/:nth-child等。
当然,并不是让大家不要应用这些属性,因为这些应该都是咱们常常应用的属性。之所以提这一点,是让大家对此有一个理解。当有两种计划能够抉择的时候,能够优先选择没有低廉属性或低廉属性更少的计划,如果每次都这样的抉择,网站的性能会在人不知;鬼不觉中失去肯定的晋升。
2.5 防止应用@important命令
不倡议应用@import次要有以下两点起因。
首先,应用@import引入CSS会影响浏览器的并行下载。应用@import援用的CSS文件只有在援用它的那个css文件被下载、解析之后,浏览器才会晓得还有另外一个css须要下载,这时才去下载,而后下载后开始解析、构建render tree等一系列操作。这就导致浏览器无奈并行下载所需的款式文件。
其次,多个@import会导致下载程序错乱。在IE中,@import会引发资源文件的下载程序被打乱,即排列在@import前面的js文件先于@import下载,并且打乱甚至毁坏@import本身的并行下载。
所以不要应用这一办法,应用link标签就行。
(1)应用@import援用内部CSS文件
导入式

<style type="text/css">
@import url("css文件门路");
</style>

(2)应用link援用内部CSS文件(举荐此办法)
链接式

<link type="text/css" rel="styleSheet" href="CSS文件门路" />
1
接下来大抵说一下这两种援用内部css文件形式的区别:
link:1、属于XHTML 2、优先加载CSS文件到页面
@import1、属于CSS2.1 2、先加载HTML构造在加载CSS文件。
总结:@import相当于就是把标签放在页面的底部,所以从优化性能的角度来看,应该尽量避免应用@important命令。
2.6 优化回流与重绘
首先咱们来理解一下什么是回流(reflow)和重绘(repaint),
a.重绘:
重绘是指css款式的扭转,然而元素的大小和尺寸不变,而导致节点的从新绘制。
b.回流:
回流(reflow)是指元素的大小、地位产生了扭转,而导致了布局的变动,从而导致了布局树的从新构建和渲染。
2.6.1 缩小回流
重排会导致浏览器从新计算整个文档,从新构建渲染树,这一过程会升高浏览器的渲染速度。如下所示,有很多操作会触发回流,咱们应该防止频繁触发这些操作。

扭转font-size和font-family
扭转元素的内外边距
通过JS扭转CSS类
通过JS获取DOM元素的地位相干属性(如width/height/left等)
CSS伪类激活
滚动滚动条或者扭转窗口大小
此外,某些CSS属性具备更好的回流性能。如应用Flex时,比应用inline-block和float时重排更快,所以在布局时能够优先思考Flex。
2.6.2 防止不必要的重绘
当元素的外观(如color,background,visibility等属性)产生扭转时,会触发重绘。在网站的应用过程中,重绘是无奈防止的。不过,浏览器对此做了优化,它会将屡次的重排、重绘操作合并为一次执行。不过咱们仍须要防止不必要的重绘,如页面滚动时触发的hover事件,能够在滚动的时候禁用hover事件,这样页面在滚动时会更加晦涩。
此外,咱们编写的CSS中动画相干的代码越来越多,咱们曾经习惯于应用动画来晋升用户体验。咱们在编写动画时,也该当参考上述内容,缩小重绘重排的触发。
最初须要留神的是,用户的设施可能并没有设想中的那么好,至多不会有咱们的开发机器那么好。咱们能够借助Chrome的开发者工具进行CPU降速,而后再进行相干的测试,降速办法如下图所示。

如果须要在挪动端拜访的,最好将速度限制更低,因为挪动端的性能往往更差。
2.7 去除无用的CSS
个别状况下,会存在这两种无用的CSS代码:一种是不同元素或者其余状况下的反复代码,一种是整个页面内没有失效的CSS代码。对于前者,在编写的代码时候,咱们应该尽可能地提取公共类,缩小反复。对于后者,在不同开发者进行代码保护的过程中,总会产生不再应用的CSS的代码,当然一个人编写时也有可能呈现这一问题。而这些无用的CSS代码不仅会减少浏览器的下载量,还会减少浏览器的解析工夫,这对性能来说是很大的耗费。所以咱们须要找到并去除这些无用代码。