本文做者:高峰,360奇舞团前端工程师,W3C性能工做组成员,同时参与WOT工做组的学习。css
咱们都知道对于网站来讲,性能相当重要,CSS做为页面渲染和内容展示的重要环节,影响着用户对整个网站的第一体验。所以,与其相关的性能优化是不容忽视的。html
对于性能优化咱们经常在项目完成时才去考虑,常常被推迟到项目的末期,甚至到暴露出严重的性能问题时才进行性能优化,相信大多数人对此深有体会。前端
笔者认为,为了更多地避免这一状况,首先要重视起性能优化相关的工做,将其贯穿到整个产品设计与开发中。其次,就是了解性能相关的内容,在项目开发过程当中,天然而然地进行性能优化。最后,也是最最重要的,那就是从如今开始实施优化。html5
推荐你们阅读下奇舞周刊以前推的《嗨,送你一张Web性能优化地图》1这篇文章,可以帮助你们对性能优化须要作的事以及须要考虑的问题造成一个总体的概念。webpack
本文将会详细介绍CSS性能优化相关的技巧,笔者将它们分为实践型和建议型两类,共8个小技巧。实践型技巧可以快速地应用在项目中,可以很好地提高性能,也是笔者常用的,建议你们尽快在项目中实践。建议型技巧中,有的可能对性能影响并不显著,有的平时你们也并不会那么用,因此笔者不会着重讲述,读者们能够根据自身状况了解一下便可。git
在正式开始以前,须要你们对于浏览器的工做原理2有些必定的了解,须要的小伙伴能够先简单了解下。github
下面咱们开始介绍实践型的4个优化技巧,先从首屏关键CSS开始。web
性能优化中有一个重要的指标——首次有效绘制(First Meaningful Paint,简称FMP)即指页面的首要内容(primary content)出如今屏幕上的时间。这一指标影响用户看到页面前所需等待的时间,而**内联首屏关键CSS(即Critical CSS,能够称之为首屏关键CSS)**能减小这一时间。gulp
你们应该都习惯于经过link标签引用外部CSS文件。但须要知道的是,将CSS直接内联到HTML文档中能使CSS更快速地下载。而使用外部CSS文件时,须要在HTML文档下载完成后才知道所要引用的CSS文件,而后才下载它们。因此说,内联CSS可以使浏览器开始页面渲染的时间提早,由于在HTML下载完成以后就能渲染了。浏览器
既然内联CSS可以使页面渲染的开始时间提早,那么是否能够内联全部的CSS呢?答案显然是否认的,这种方式并不适用于内联较大的CSS文件。由于初始拥塞窗口3存在限制(TCP相关概念,一般是 14.6kB,压缩后大小),若是内联CSS后的文件超出了这一限制,系统就须要在服务器和浏览器之间进行更屡次的往返,这样并不能提早页面渲染时间。所以,咱们应当只将渲染首屏内容所需的关键CSS内联到HTML中。
既然已经知道内联首屏关键CSS可以优化性能了,那下一步就是如何肯定首屏关键CSS了。显然,咱们不须要手动肯定哪些内容是首屏关键CSS。Github上有一个项目Critical CSS4,能够将属于首屏的关键样式提取出来,你们能够看一下该项目,结合本身的构建工具进行使用。固然为了保证正确,你们最好再亲自确认下提取出的内容是否有缺失。
不过内联CSS有一个缺点,内联以后的CSS不会进行缓存,每次都会从新下载。不过如上所说,若是咱们将内联后的文件大小控制在了14.6kb之内,这彷佛并非什么大问题。
如上,咱们已经介绍了为何要内联关键CSS以及如何内联,那么剩下的CSS咱们怎么处理好呢?建议使用外部CSS引入剩余CSS,这样可以启用缓存,除此以外还能够异步加载它们。
CSS会阻塞渲染,在CSS文件请求、下载、解析完成以前,浏览器将不会渲染任何已处理的内容。有时,这种阻塞是必须的,由于咱们并不但愿在所需的CSS加载以前,浏览器就开始渲染页面。那么将首屏关键CSS内联后,剩余的CSS内容的阻塞渲染就不是必需的了,可使用外部CSS,而且异步加载。
那么如何实现CSS的异步加载呢?有如下四种方式能够实现浏览器异步加载CSS。
第一种方式是使用JavaScript动态建立样式表link元素,并插入到DOM中。
// 建立link标签 const myCSS = document.createElement( "link" ); myCSS.rel = "stylesheet"; myCSS.href = "mystyles.css"; // 插入到header的最后位置 document.head.insertBefore( myCSS, document.head.childNodes[ document.head.childNodes.length - 1 ].nextSibling ); 复制代码
第二种方式是将link元素的media
属性设置为用户浏览器不匹配的媒体类型(或媒体查询),如media="print"
,甚至能够是彻底不存在的类型media="noexist"
。对浏览器来讲,若是样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的状况下再进行下载。
固然,这么作只是为了实现CSS的异步加载,别忘了在文件加载完成以后,将media
的值设为screen
或all
,从而让浏览器开始解析CSS。
<link rel="stylesheet" href="mystyles.css" media="noexist" onload="this.media='all'"> 复制代码
与第二种方式类似,咱们还能够经过rel
属性将link
元素标记为alternate
可选样式表,也能实现浏览器异步加载。一样别忘了加载完成以后,将rel
改回去。
<link rel="alternate stylesheet" href="mystyles.css" onload="this.rel='stylesheet'"> 复制代码
上述的三种方法都较为古老。如今,rel="preload"5这一Web标准指出了如何异步加载资源,包括CSS类资源。
<link rel="preload" href="mystyles.css" as="style" onload="this.rel='stylesheet'"> 复制代码
注意,as
是必须的。忽略as
属性,或者错误的as
属性会使preload
等同于XHR
请求,浏览器不知道加载的是什么内容,所以此类资源加载优先级会很是低。as
的可选值能够参考上述标准文档。
看起来,rel="preload"
的用法和上面两种没什么区别,都是经过更改某些属性,使得浏览器异步加载CSS文件但不解析,直到加载完成并将修改还原,而后开始解析。
可是它们之间其实有一个很重要的不一样点,那就是使用preload,比使用不匹配的media
方法可以更早地开始加载CSS。因此尽管这一标准的支持度还不完善,仍建议优先使用该方法。
该标准如今已是候选标准,相信浏览器会逐渐支持该标准。在各浏览器的支持度以下图所示。
从上图能够看出这一方法在如今的浏览器中支持度不算乐观,不过咱们能够经过loadCSS6进行polyfill,因此支持不支持,这都不是事儿。
性能优化时有一个最容易想到,也最常使用的方法,那就是文件压缩,这一方案每每效果显著。
文件的大小会直接影响浏览器的加载速度,这一点在网络较差时表现地尤其明显。相信你们都早已习惯对CSS进行压缩,如今的构建工具,如webpack、gulp/grunt、rollup等也都支持CSS压缩功能。压缩后的文件可以明显减少,能够大大下降了浏览器的加载时间。
虽然文件压缩可以下降文件大小。但CSS文件压缩一般只会去除无用的空格,这样就限制了CSS文件的压缩比例。那是否还有其余手段来精简CSS呢?答案显然是确定的,若是压缩后的文件仍然超出了预期的大小,咱们能够试着找到并删除代码中无用的CSS。
通常状况下,会存在这两种无用的CSS代码:一种是不一样元素或者其余状况下的重复代码,一种是整个页面内没有生效的CSS代码。对于前者,在编写的代码时候,咱们应该尽量地提取公共类,减小重复。对于后者,在不一样开发者进行代码维护的过程当中,总会产生再也不使用的CSS的代码,固然一我的编写时也有可能出现这一问题。而这些无用的CSS代码不只会增长浏览器的下载量,还会增长浏览器的解析时间,这对性能来讲是很大的消耗。因此咱们须要找到并去除这些无用代码。
固然,若是手动删除这些无用CSS是很低效的。咱们能够借助Uncss7库来进行。Uncss能够用来移除样式表中的无用CSS,而且支持多文件和JavaScript注入的CSS。
前面已经说完了实践型的4个优化技巧,下面咱们介绍下建议型的4个技巧。
大多数朋友应该都知道CSS选择器的匹配是从右向左进行的,这一策略致使了不一样种类的选择器之间的性能也存在差别。相比于#markdown-content-h3
,显然使用#markdown .content h3
时,浏览器生成渲染树(render-tree)所要花费的时间更多。由于后者须要先找到DOM中的全部h3
元素,再过滤掉祖先元素不是.content
的,最后过滤掉.content
的祖先不是#markdown
的。试想,若是嵌套的层级更多,页面中的元素更多,那么匹配所要花费的时间代价天然更高。
不过现代浏览器在这一方面作了不少优化,不一样选择器的性能差异并不明显,甚至能够说差异甚微。此外不一样选择器在不一样浏览器中的性能表现8也不彻底统一,在编写CSS的时候没法兼顾每种浏览器。鉴于这两点缘由,咱们在使用选择器时,只须要记住如下几点,其余的能够全凭喜爱。
h3#markdown-content
,这样画蛇添足,还会下降效率。若是你们对于上面这几点还存在疑问,笔者建议你们选择如下几种CSS方法论之一(BEM9,OOCSS10,SUIT11,SMACSS12,ITCSS13,Enduring CSS14等)做为CSS编写规范。使用统一的方法论可以帮助你们造成统一的风格,减小命名冲突,也能避免上述的问题,总之好处多多,若是你尚未使用,就赶快用起来吧。
Tips:为何CSS选择器是从右向左匹配的?
CSS中更多的选择器是不会匹配的,因此在考虑性能问题时,须要考虑的是如何在选择器不匹配时提高效率。从右向左匹配就是为了达成这一目的的,经过这一策略可以使得CSS选择器在不匹配的时候效率更高。这样想来,在匹配时多耗费一些性能也可以想的通了。
在浏览器绘制屏幕时,全部须要浏览器进行操做或计算的属性相对而言都须要花费更大的代价。当页面发生重绘时,它们会下降浏览器的渲染性能。因此在编写CSS时,咱们应该尽可能减小使用昂贵属性,如box-shadow
/border-radius
/filter
/透明度/:nth-child
等。
固然,并非让你们不要使用这些属性,由于这些应该都是咱们常用的属性。之因此提这一点,是让你们对此有一个了解。当有两种方案能够选择的时候,能够优先选择没有昂贵属性或昂贵属性更少的方案,若是每次都这样的选择,网站的性能会在不知不觉中获得必定的提高。
在网站的使用过程当中,某些操做会致使样式的改变,这时浏览器须要检测这些改变并从新渲染,其中有些操做所耗费的性能更多。咱们都知道,当FPS为60时,用户使用网站时才会感到流畅。这也就是说,咱们须要在16.67ms内完成每次渲染相关的全部操做,因此咱们要尽可能减小耗费更多的操做。
重排会致使浏览器从新计算整个文档,从新构建渲染树,这一过程会下降浏览器的渲染速度。以下所示,有不少操做会触发重排,咱们应该避免频繁触发这些操做。
font-size
和font-family
此外,咱们还能够经过CSS Trigger15查询哪些属性会触发重排与重绘。
值得一提的是,某些CSS属性具备更好的重排性能。如使用Flex
时,比使用inline-block
和float
时重排更快,因此在布局时能够优先考虑Flex
。
当元素的外观(如color,background,visibility等属性)发生改变时,会触发重绘。在网站的使用过程当中,重绘是没法避免的。不过,浏览器对此作了优化,它会将屡次的重排、重绘操做合并为一次执行。不过咱们仍须要避免没必要要的重绘,如页面滚动时触发的hover事件,能够在滚动的时候禁用hover事件,这样页面在滚动时会更加流畅。
此外,咱们编写的CSS中动画相关的代码愈来愈多,咱们已经习惯于使用动画来提高用户体验。咱们在编写动画时,也应当参考上述内容,减小重绘重排的触发。除此以外咱们还能够经过硬件加速16和will-change17来提高动画性能,本文不对此展开详细介绍,感兴趣的小伙伴能够点击连接进行查看。
最后须要注意的是,用户的设备可能并无想象中的那么好,至少不会有咱们的开发机器那么好。咱们能够借助Chrome的开发者工具进行CPU降速,而后再进行相关的测试,降速方法以下图所示。
若是须要在移动端访问的,最好将速度限制更低,由于移动端的性能每每更差。
最后提一下,不要使用@import引入CSS,相信你们也不多使用。
不建议使用@import主要有如下两点缘由。
首先,使用@import引入CSS会影响浏览器的并行下载。使用@import引用的CSS文件只有在引用它的那个css文件被下载、解析以后,浏览器才会知道还有另一个css须要下载,这时才去下载,而后下载后开始解析、构建render tree等一系列操做。这就致使浏览器没法并行下载所需的样式文件。
其次,多个@import会致使下载顺序紊乱。在IE中,@import会引起资源文件的下载顺序被打乱,即排列在@import后面的js文件先于@import下载,而且打乱甚至破坏@import自身的并行下载。
因此不要使用这一方法,使用link标签就好了。
至此,咱们介绍完了CSS性能优化的4个实践型技巧和4个建议型技巧,在了解这些技巧以后,CSS的性能优化从如今就能够开始了。不要犹豫了,尽快开始吧。
特别感谢@anjia(安佳)、@刘宇晨、@hxl(黄小璐)、@刘观宇的辛苦审校,感谢大家对于文章结构和内容提出的宝贵建议。
- Efficiently Rendering CSS
- How to write CSS for a great performance web application
- CSS performance revisited: selectors, bloat and expensive styles
- Avoiding Unnecessary Paints
- Five CSS Performance Tools to Speed up Your Website
- How and Why You Should Inline Your Critical CSS
- Render blocking css
- Modern Asynchronous CSS Loading
- Preload