CSS对于呈现页面相当重要 - 在找到,下载和解析全部CSS以前,浏览器不会开始呈现 - 所以咱们必须尽量快地将其加载到用户的设备上。 关键路径上的任何延迟都会影响咱们的“开始渲染”并让用户看到空白屏幕。css
从广义上讲,这就是CSS对性能相当重要的缘由:html
考虑到这一点,咱们须要尽快构建DOM和CSSOM。 在大多数状况下,构建DOM相对较快:您的第一个HTML响应是DOM。 可是,因为CSS几乎老是HTML的子资源,所以构建CSSOM一般须要更长的时间。程序员
在这篇文章中,我想看看CSS如何证实是网络上的一个重大瓶颈(自己和其余资源)以及咱们如何缓解它,从而缩短关键路径并缩短开始渲染的时间。浏览器
若是你有能力,减小Start Render时间的最有效方法之一就是使用Critical CSS模式:识别Start Render所需的全部样式(一般是首屏所需的样式), 将它们内联到文档的<head>中的<style>标记中,并从这里异步加载剩余的样式表。缓存
虽然这种策略是有效的,但并不简单:高度动态的网站很难从中提取样式,流程须要自动化,咱们必须对折叠率甚至是什么作出假设,很难捕获边缘状况和工具 仍处于相对初期阶段。 若是您正在使用大型或遗留代码库,事情会变得更加困难......安全
若是实现关键CSS很是棘手 - 它可能只是一种选择,咱们将主要的CSS文件拆分为其各自的媒体查询。 这样作的实际结果是浏览器会......微信
基本上,浏览器有效地延迟了不须要渲染当前视图的任何CSS。网络
<link rel="stylesheet" href="all.css" />
若是咱们将全部CSS捆绑到一个文件中,那么它会这样子加载:app
若是咱们能够将单个全渲染阻塞文件拆分为各自的媒体查询:dom
<link rel="stylesheet" href="all.css" media="all" /> <link rel="stylesheet" href="small.css" media="(min-width: 20em)" /> <link rel="stylesheet" href="medium.css" media="(min-width: 64em)" /> <link rel="stylesheet" href="large.css" media="(min-width: 90em)" /> <link rel="stylesheet" href="extra-large.css" media="(min-width: 120em)" /> <link rel="stylesheet" href="print.css" media="print" />
而后咱们看到网络以不一样方式处理文件:
浏览器仍将下载全部CSS文件,但它只会阻止渲染完成当前上下文所需的文件。
咱们能够作的下一件事就是帮助Start Render更加简单。 避免在CSS文件中使用@import。
@import,根据它的工做原理,很慢。 对于Start Render性能来讲真的很是糟糕。 这是由于咱们正在关键路径上积极建立更多循环路径:
如下HTML:
<link rel="stylesheet" href="all.css" />
包含在all.css中@import
@import url(imported.css);
咱们最终获得这样的瀑布图:
经过简单地将其展平为两个<link rel =“stylesheet”/>和去掉@imports:
<link rel="stylesheet" href="all.css" /> <link rel="stylesheet" href="imported.css" />
咱们获得一个更健康的瀑布图:
要彻底理解本节,咱们首先须要了解浏览器的预装载扫描程序:全部主流浏览器都实现了一般称为预装载扫描程序的辅助惰性解析器。 浏览器的主要解析器负责构建DOM,CSSOM,运行JavaScript等,而且随着文档的不一样部分阻止它而不断中止和启动。 Preload Scanner能够安全地跳过主解析器并扫描HTML的其他部分,以发现对其余子资源(例如CSS文件,JS,图像)的引用。 一旦发现它们,Preload Scanner就会开始下载它们,以便主要解析器接收它们并在之后执行/应用它们。 Preload Scanner的推出使网页性能提升了大约19%,全部这些都不须要开发人员参与。 这对用户来讲是个好消息!
咱们做为开发人员须要警戒的一件事是无心中隐藏了Preload Scanner中可能发生的事情。 稍后会详细介绍。
本节介绍WebKit和Blink的Preload Scanner中的错误,以及Firefox和IE / Edge的Preload Scanner中的低效率。
Firefox和IE / Edge:将@import放在HTML中的JS和CSS以前
在Firefox和IE / Edge中,Preload Scanner彷佛没有使用<script src =“”>或<link rel =“stylesheet”/>以后定义的任何@import。
这意味着这个HTML:
<script src="app.js"></script> <style> @import url(app.css); </style>
将产生这个瀑布图:
因为无效预装载扫描程序致使Firefox失去并行化(N.B.在IE / Edge中出现相同的瀑布。)
这个问题的直接解决方案是交换<script>或<link rel =“stylesheet”/>和<style>块。 可是,当咱们更改依赖顺序时,这可能会破坏事物(想一想他们之间的关联)。
这个问题的首选解决方案是彻底避免使用@import并使用第二个<link rel =“stylesheet”/>:
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="app.css" />
瀑布图以下:
两个<link rel =“stylesheet”/> s让咱们回到并行化。 (N.B. IE / Edge中出现相同的瀑布。)
仅当您的@import URL缺乏引号(“)时,WebKit和Blink的行为与Firefox和IE / Edge彻底相同。这意味着WebKit和Blink中的Preload Scanner存在错误。
简单地将@import包装在引号中将解决问题,您无需从新排序任何内容。 不过,和之前同样,个人建议是彻底避免使用@import,而是选择第二个<link rel =“stylesheet”/>。
以前
<link rel="stylesheet" href="style.css" /> <style> @import url(app.css); </style>
瀑布图:
咱们的@ import
网址中缺乏引号会破坏Chrome的预装扫描程序(N.B.在Opera和Safari中会出现相同的瀑布。)
修改以后:
<link rel="stylesheet" href="style.css" /> <style> @import url("app.css"); </style>
在咱们的@ import
网址中添加引号可修复Chrome的Preload Scanner(N.B.在Opera和Safari中也会出现相同的瀑布。)
这绝对是WebKit / Blink中的一个错误 - 缺乏引号不该该隐藏Preload Scanner中的@imported样式表。
上一节讨论了如何经过其余资源减慢CSS,本节将讨论CSS如何无心中延迟下载资源的下载,主要是使用异步加载代码段插入的JavaScript,以下所示:
<script> var script = document.createElement('script'); script.src = "analytics.js"; document.getElementsByTagName('head')[0].appendChild(script); </script>
在全部浏览器中都存在一种有意和预期的迷人行为,但我从未遇到过一个了解它的开发人员。 当您考虑它能够带来的巨大性能影响时,这是很是使人惊讶的:
若是有任何当前CSS在加载,浏览器将不会执行<script>。
<link rel="stylesheet" href="slow-loading-stylesheet.css" /> <script> console.log("I will not run until slow-loading-stylesheet.css is downloaded."); </script>
这是设计的。 这是故意的。 当前正在下载任何CSS时,HTML中的任何同步<script>都不会执行。 这是一个简单的防护策略来解决<script>可能会询问页面样式的边缘状况:若是脚本在CSS到达并被解析以前询问页面的颜色,那么JavaScript给咱们的答案 多是不正确或陈旧的。 为了缓解这种状况,浏览器在构造CSSOM以前不会执行<script>。
这样作的结果是,CSS下载时间的任何延迟都会对你的异步片断产生连锁反应。 用一个例子能够很好地说明这一点。
若是咱们在异步片断前放置<link rel =“stylesheet”/>,则在下载和解析该CSS文件以前它不会运行。
<link rel="stylesheet" href="app.css" /> <script> var script = document.createElement('script'); script.src = "analytics.js"; document.getElementsByTagName('head')[0].appendChild(script); </script>
根据这个顺序,咱们能够清楚地看到JavaScript文件甚至在构建CSSOM以前甚至没有开始下载。 咱们彻底失去了任何并行化:
在异步代码段以前使用样式表能够撤消咱们并行化的机会。
有趣的是,Preload Scanner但愿提早得到对analytics.js的引用,可是咱们无心中隐藏了它:“analytics.js”是一个字符串,而且在<<以前不会成为可标记的src属性 script>元素存在于DOM中。 这是我早些时候说的,当我稍后再说这个时。
第三方供应商提供这样的异步代码片断以更安全地加载脚本是很常见的。 开发人员对这些第三方持怀疑态度,并在页面后面放置异步片断也是很常见的。 虽然这是出于最好的意图 - 我不想在我本身的资产以前放置第三方<script>! - 一般多是净损失。 事实上,谷歌分析甚至告诉咱们该作什么,他们是对的:
将此代码做为第一项复制并粘贴到您要跟踪的每一个网页的<HEAD>中。
因此个人建议是:
若是您的<script> ... </ script>块不依赖于CSS,请将它们放在样式表上方。
如下是咱们转向此模式时会发生的代码:
<script> var script = document.createElement('script'); script.src = "analytics.js"; document.getElementsByTagName('head')[0].appendChild(script); </script> <link rel="stylesheet" href="app.css" />
交换样式表和异步代码片断能够从新得到并行化。
如今您能够看到咱们已经彻底从新得到了并行化,而且页面加载速度提升了近2倍。
在CSS以前放置任何非CSSOM查询JavaScript; 在CSS以后放置任何CSSOM查询JavaScript
更进一步,除了异步加载片断以外,咱们应该如何更普适地加载CSS和JavaScript? 为了解决这个问题,我提出了如下问题并从那里开始工做:
若是:
那么 - 假设没有相互依赖 - 哪一个更快/更喜欢?
Script -> style;
style -> script?
答案是:
若是文件不相互依赖,那么您应该将阻塞脚本置于阻塞样式之上 - 没有必要将JavaScript执行延迟到JavaScript实际上不依赖的CSS。
(Preload Scanner确保即便在脚本上阻止了DOM构造,CSS仍然会并行下载。)
若是你的一些JavaScript作了但有些不依赖于CSS,那么加载同步JavaScript和CSS的绝对最佳顺序是将JavaScript分红两部分并将其加载到CSS的任何一侧:
<!-- This JavaScript executes as soon as it has arrived. --> <script src="i-need-to-block-dom-but-DONT-need-to-query-cssom.js"></script> <link rel="stylesheet" href="app.css" /> <!-- This JavaScript executes as soon as the CSSOM is built. --> <script src="i-need-to-block-dom-but-DO-need-to-query-cssom.js"></script>
使用这种加载模式,咱们能够按最佳顺序进行下载和执行。 我为下面的截图中的微小细节道歉,但但愿你能看到表明JavaScript执行的小粉红色标记。
entry(1)是计划在其余文件到达和/或执行时执行某些JavaScript的HTML;
entry(2)执行它到达的那一刻;
entry(3)是CSS,因此不执行任何JavaScript;
在CSS完成以前,entry(4)实际上不会执行。
注: 您必须根据本身的特定用例测试此模式:根据您以前的CSS JavaScript文件与CSS自己之间的文件大小和执行成本是否存在巨大差别,可能会有不一样的结果。 测试,测试,测试。
这个最终策略是一个相对较新的策略,对感知性能和渐进式渲染有很大好处。 它也很是友好。
在HTTP / 1.1中,咱们将全部样式链接到一个主要包中是很典型的。 咱们称之为app.css:
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="app.css" /> </head> <body> <header class="site-header"> <nav class="site-nav">...</nav> </header> <main class="content"> <section class="content-primary"> <h1>...</h1> <div class="date-picker">...</div> </section> <aside class="content-secondary"> <div class="ads">...</div> </aside> </main> <footer class="site-footer"> </footer> </body>
这带来三个关键的低效率:
使用HTTP / 2,咱们能够开始解决点(1)和(2):
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="core.css" /> <link rel="stylesheet" href="site-header.css" /> <link rel="stylesheet" href="site-nav.css" /> <link rel="stylesheet" href="content.css" /> <link rel="stylesheet" href="content-primary.css" /> <link rel="stylesheet" href="date-picker.css" /> <link rel="stylesheet" href="content-secondary.css" /> <link rel="stylesheet" href="ads.css" /> <link rel="stylesheet" href="site-footer.css" /> </head> <body> <header class="site-header"> <nav class="site-nav">...</nav> </header> <main class="content"> <section class="content-primary"> <h1>...</h1> <div class="date-picker">...</div> </section> <aside class="content-secondary"> <div class="ads">...</div> </aside> </main> <footer class="site-footer"> </footer> </body>
如今咱们正在解决冗余问题,由于咱们可以加载更适合页面的CSS,而不是不加选择地下载全部内容。 这减小了关键路径上阻塞CSS的大小。
咱们还能够采用更有意思的缓存策略,只缓存破坏须要它的文件,并保持其他部分不受影响。
咱们尚未解决的问题是它仍然阻止渲染 - 咱们仍然只有最慢的样式表。 这意味着若是不管出于何种缘由,site-footer.css须要很长时间才能下载,浏览器没法开始渲染.site-header。
可是,因为Chrome最近发生了变化(我相信版本69),以及Firefox和IE / Edge中已经存在的行为,<link rel =“stylesheet”/> 只会阻止后续内容的呈现,而不是 整页。 这意味着咱们如今可以像这样构建咱们的页面:
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="core.css" /> </head> <body> <link rel="stylesheet" href="site-header.css" /> <header class="site-header"> <link rel="stylesheet" href="site-nav.css" /> <nav class="site-nav">...</nav> </header> <link rel="stylesheet" href="content.css" /> <main class="content"> <link rel="stylesheet" href="content-primary.css" /> <section class="content-primary"> <h1>...</h1> <link rel="stylesheet" href="date-picker.css" /> <div class="date-picker">...</div> </section> <link rel="stylesheet" href="content-secondary.css" /> <aside class="content-secondary"> <link rel="stylesheet" href="ads.css" /> <div class="ads">...</div> </aside> </main> <link rel="stylesheet" href="site-footer.css" /> <footer class="site-footer"> </footer> </body>
这样作的实际结果是,咱们如今可以逐步呈现咱们的页面,在页面可用时有效地将页面输送样式添加到页面中。
在目前不支持这种新行为的浏览器中,咱们不会遇到性能降低:咱们会回到原来的行为,咱们只有最慢的CSS文件加载完成才会展现页面。
本文中有不少要消化的内容。 它最终超越了我最初打算写的帖子。 尝试总结加载CSS的最佳网络性能实践:
Lazyload Start Start Render不须要的任何CSS:
拆分关键CSS; 或将您的CSS拆分为媒体查询。
避免@import:
在你的HTML中; 特别是在CSS中; 并提防Preload Scanner的奇怪之处。
警戒同步CSS和JavaScript命令:
在CSSOM完成以前,CSS以后定义的JavaScript将没法运行 因此若是你的JavaScript不依赖于你的CSS,在CSS以前加载它; 若是它取决于你的CSS,在CSS以后加载它。
我上面概述的全部内容都遵循规范或已知/预期的行为,可是,一如既往,本身测试一切。 虽然这在理论上都是正确的,但在实践中事情老是有所不一样。 套用中国的一句老话,实践出真知啊。
建立了一个程序员交流微信群,你们进群交流IT技术
若是已过时,能够添加博主微信号15706211347,拉你进群