CSS 加载新方式
Chrome 浏览器有意改变<link rel="stylesheet">的加载方式,当其出现在<body>中时,这一变化将更加明显。笔者决定在本文中进行详细说明这种改变可能带来影响与好处。
一.目前CSS文件的加载方式
<head>
<link rel="stylesheet" href="/all-of-my-styles.css">
</head>
<body>
…content…
</body>
CSS 会阻碍渲染,因此在all-of-my-styles.css全部加载完之前,用户就只能面对一片空白的屏幕。
通常,我们将某个站点的所有 CSS 样式合并为一到两个资源,这意味着用户会下载一堆当前页面根本就用不上的规则。这是因为网站可能包含许多不同类型的页面,每个页面都有自己的「组件」;而在组件级别传递 CSS 的话,会降低 HTTP/1 的性能。
然而,对 SPDY 和 HTTP/2 来说,事实却并非如此。在这些协议中,许多小资源只需要很小的代价就能完成递送,并且被独立缓存。
<head>
<link rel="stylesheet" href="/site-header.css">
<link rel="stylesheet" href="/article.css">
<link rel="stylesheet" href="/comment.css">
<link rel="stylesheet" href="/about-me.css">
<link rel="stylesheet" href="/site-footer.css">
</head>
<body>
…content…
</body>
这样一来就解决了冗余问题,但也意味着你需要知道输出<head>时页面将包含的内容,从而防止 streaming。与此同时,浏览器还是只能等待所有 CSS 样式加载完毕,才能开始渲染。如果加载 /site-footer.css的速度不够快,就会耽误所有页面的渲染。
二.目前最先进的 CSS 加载方法
<head>
<script>
// https://github.com/filamentgroup/loadCSS
!function(e){"use strict"
var n=function(n,t,o){function i(e){return f.body?e():void setTimeout(function(){i(e)})}var d,r,a,l,f=e.document,s=f.createElement("link"),u=o||"all"
return t?d=t:(r=(f.body||f.getElementsByTagName("head")[0]).childNodes,d=r[r.length-1]),a=f.styleSheets,s.rel="stylesheet",s.href=n,s.media="only x",i(function(){d.parentNode.insertBefore(s,t?d:d.nextSibling)}),l=function(e){for(var n=s.href,t=a.length;t--;)if(a[t].href===n)return e()
setTimeout(function(){l(e)})},s.addEventListener&&s.addEventListener("load",function(){this.media=u}),s.onloadcssdefined=l,l(function(){s.media!==u&&(s.media=u)}),s}
"undefined"!=typeof exports?exports.loadCSS=n:e.loadCSS=n}("undefined"!=typeof global?global:this)
</script>
<style>
/* The styles for the site header, plus: */
.main-article,
.comments,
.about-me,
footer {
display: none;
}
</style>
<script>
loadCSS("/the-rest-of-the-styles.css");
</script>
</head>
<body>
</body>
在上面的代码中,通过一些内联样式我们可以加速初始渲染,同时隐藏起还没有加载完样式的组件,并通过 JavaScript 异步地完成加载。剩余的 CSS 加载完后会重写.main-article中的display:none。
这个方法受到性能专家的推崇,他们认为这是快速完成初始渲染的好方法,并且经过实地测量确实在加载的时候快了不少。
但也存在一些不足之处。。。。。。
「1.它需要一个(小的)JavaScript 库」
这是由 WebKit 的实现方式造成的。一旦页面中添加了<link rel="stylesheet">,即使样式表是由 JavaScript 加载的,WebKit 还是会在加载完成之前阻碍渲染。
在 Firefox 和 IE/Edge 浏览器中,通过 JS 加载样式表是完全异步进行的。稳定版本的 Chrome 浏览器是通过 WebKit 的方式加载的,但在 Canary 版本中,仍然是使用 Firefox/Edge 加载方式。
「2.必须经历两个加载阶段」
在上述模式中,内联的 CSS 通过display:none隐藏了没有加载完样式的内容,直到异步加载完剩余的 CSS 样式。如果你将这些样式分派到两个或多个 CSS 文件中,这些文件有可能不按照顺序加载,导致加载过程中出现内容错乱:
内容错乱,就好比弹出广告一样,会导致用户体验挫败,必须全力消灭。
既然有两个加载阶段,你就必须决定渲染的先后顺序。你当然会想首先渲染「位置显要」的内容。但是,所谓的「位置」是根据窗口大小来决定的。因此,问题来了,你得找出一把「万能」钥匙。
三.一个更简单、更好的方法
<head>
</head>
<body>
<!-- HTTP/2 push this resource, or inline it, whichever's faster -->
<link rel="stylesheet" href="/site-header.css">
<header>…</header>
<link rel="stylesheet" href="/article.css">
<main>…</main>
<link rel="stylesheet" href="/comment.css">
<section class="comments">…</section>
<link rel="stylesheet" href="/about-me.css">
<section class="about-me">…</section>
<link rel="stylesheet" href="/site-footer.css">
<footer>…</footer>
</body>
计划是这样的:针对每个<link rel="stylesheet">,加载样式表时我们阻止渲染它的后续内容,但是允许渲染它之前的内容。样式表是并行加载的,但是按照一定的顺序显示。这使得<link rel="stylesheet">的效用与<script src="…"></script>相近。
假设网站 header、正文和 footer 的 CSS 已经加载完毕,但其余内容仍在等待,那么页面会是这样的:
- Header:已渲染
- 正文:已渲染
- 评论部分:未渲染,它前面的 CSS 还未被加载(
/comment.css)。 - 关于本站:未渲染。它前面的 CSS 还未被加载(
/comment.css)。 - Footer:未渲染。尽管它本身的 CSS 已加载完成,但它前面的 CSS 还未被加载(
/comment.css)。
这是一个按顺序渲染的页面。你不需要决定哪部分内容在「显要位置」,只要在页面组件第一次实例化之前引入该组件的 CSS 即可。它完全兼容 Streaming,因为除非你需要,否则不必要输出<link>。
当使用内容决定布局的布局系统时(例如表格和 flexbox),要注意避免加载时出现内容错位。这不是什么新问题了,但是分步渲染会使得它出现得更为频繁。你可以通过 hack flexbox 来解决,但对整体页面布局来说,使用 CSS grid 工具效果更佳(不过对小一些的组件来说,flexbox 还是很棒的)。
四.Chrome浏览器的改变
HTML 规范并没有规定 CSS 应当怎样阻止页面渲染,它不鼓励在 body 中使用<link rel="stylesheet">,但是所有的浏览器都允许使用。当然了,浏览器们在处理 body 中的 link 时都有自己的方法:
Chrome和Safari:一旦发现
<link rel="stylesheet">就停止渲染,并且在已发现的样式表全部完成加载之前不会开始渲染。这会导致<link>前未被渲染的内容也被阻塞。Firefox: head中的
<link rel="stylesheet">会阻塞渲染,直至所有已发现的样式表加载完毕,body中的<link rel="stylesheet">并不阻塞任何渲染,除非某个 head 中的样式表已经阻塞了渲染,这会导致无样式的内容出现闪烁(FOUC)。IE/Edge: 阻塞解析器直到样式表加载完毕,但是允许渲染
<link>之前的内容。
在 Chrome 团队,我们喜欢 IE/Edge 的方式,所以打算跟它看齐。这就允许上文描述的渐进式 CSS 渲染方式。我们正在努力把它变成标准,从允许<body>中的<link>开始。
目前 Chrome/Safari 采用的方式是向下兼容的,带来的问题是阻塞渲染的时间比实际需要的长。Firefox 的方式稍微复杂一些,但有个解决的方法:
「Firefixing!」
因为 Firefox 并不总是为了<body>中的<link>阻塞渲染,我们得为这个多花点功夫来避免 FOUC。谢天谢地这很容易,因为<script>会阻塞解析,同时也会等挂起的样式表完成加载:
<link rel="stylesheet" href="/article.css"><script> </script>
<main>…</main>
此处的<script>元素必须是非空的,但加个空格足矣。
Firefox 和 Edge/IE 可以实现很美好的渐进式渲染,而 Chrome 和 Safari 在所有 CSS 加载完毕之前只能给你看一张白屏。目前 Chrome/Safari 采用的方式怎么都比将所有的样式表都放<head>里要强,所以你现在就可以开始采用这个方法了。后面几个月,Chrome 会迁移到 Edge 的模式,这样用户就能体验更快的渲染速度了。
就是这样!通过更简单的方法加载你需要的 CSS,强力提升渲染速度。
五.快速定位 CSS 加载问题
那么问题来了,怎么样才能知道是不是 css 加载影响了页面的性能呢?只有定位到问题确实是 css ,老板才会给你时间和人力来优化这方面的问题对不对?

笔者之前做过前端优化的工作,国内外的前端性能优化工具也使用了不少,现阶段可以较好实现这个定位页面慢加载因素的工具有:
OneAPM Browser Insight、AppDynamics、Ruxit,大家有兴趣的话可以去尝试下。
注:本文原文作者为 Jake Archibald,由 OneAPM 运营人员翻译整理
原文地址:https://jakearchibald.com/2016/link-in-body/
Browser Insight 是一个基于真实用户的 Web 前端性能监控平台,能够帮大家定位网站性能瓶颈,网站加速效果可视化;支持浏览器、微信、App 浏览 HTML 和 HTML5 页面。想阅读更多技术文章,请访问 OneAPM 官方技术博客。
本文转自 OneAPM 官方博客
CSS 加载新方式的更多相关文章
- 在浏览器窗口中加载新的url
通常,在前端页面中如果需要跳转到指定页面,可以通过<a>标签进行跳转.而在某些情况下,比如ajax调用之后想直接跳转到指定页面,想跳转页面不能再用<a>标签实现.此时,可以通过 ...
- vue-loader 调用了cssLoaders方法配置了css加载器属性。
module: { loaders: [ // 这里也是相应的配置,test就是匹配文件,loader是加载器, { test: /\.vue$/, loader: 'vue' }, { test: ...
- 定时器详解和应用、js加载阻塞、css加载阻塞
1.setTimeout().setInterval()详解和应用 1.1 详解: setTimeout.setInterval执行时机 1.2 存在问题: setInterval重复定时器可能存在的 ...
- Github css加载失败,样式混乱解决办法
github被墙的解决办法 Github css加载失败,样式混乱解决办法 打开cmd,输入 nslookup github.com 8.8.8.8 ,下面就会显示出github的服务器地址列 ...
- JavaScript动态加载script方式引用百度地图API 拓展---JavaScript的Promise
上一篇博客JavaScript动态加载script方式引用百度地图API,Uncaught ReferenceError: BMap is not defined 这篇文章中我接触到一个新的单词:Pr ...
- css加载优化
<head> <script> // https://github.com/filamentgroup/loadCSS !function(e){"use stric ...
- 为网格布局图片打造的超炫 CSS 加载动画
今天,我想与大家分享一些专门为网格布局的图像制作的很酷的 CSS 加载动画效果.您可以把这些效果用在你的作品集,博客或任何你想要的网页中.设置很简单.我们使用了下面这些工具库来实现这个效果: Norm ...
- 炫!一组单元素实现的 CSS 加载进度提示效果
之前的文章个大家分享过各种类型的加载效果(Loading Effects),这里再给大家奉献一组基于单个元素实现的 CSS 加载动画集合.这些加载效果都是基于一个 DIV 元素实现的,十分强悍. 温馨 ...
- Android三种基本的加载网络图片方式(转)
Android三种基本的加载网络图片方式,包括普通加载网络方式.用ImageLoader加载图片.用Volley加载图片. 1. [代码]普通加载网络方式 ? 1 2 3 4 5 6 7 8 9 10 ...
随机推荐
- Effective C# 学习笔记(原则二:为你的常量选择readonly而不是const)
原则二.为你的常量选择readonly而不是const Prefer readonly to const 对于常量,C#里面有两个不同的版本:运行时常量(readonly)和编译时常量(co ...
- 如何在Eclipse中配置Tomcat
1.Eclipse EE 配置Tomcat Eclipse EE 主要用于Java Web开发和J2EE项目开发.Eclipse EE中配置Tomcat比较简单,新建一个Tomcat Server即可 ...
- 老工程升级到VS2010或以上时会出现 libc.lib 解决方法
有些网上的工程都比较老,比如用2003之类.一般会有个静态libc.lib.在新版本里已经没有这个库,被微软无情的抛弃. 编译时会出现动态库找不到: 1>LINK : fatal error L ...
- VC中实现GCC的2个比较常用的位运算函数
在GCC中内嵌了两个位运算的函数,但在VC中并没有这两个函数(有相似函数). //返回前导的0的个数. int __builtin_clz (unsigned int x) //返回后面的0个个数,和 ...
- c++大数模板
自己写的大数模板,参考了小白书上的写法,只是实现了加减乘法,不支持负数,浮点数.. 除法还没写o(╯□╰)o以后再慢慢更吧.. 其实除法我用(xie)的(bu)少(lai),乘法写过fft,这模板还是 ...
- android bluetooth UUID蓝牙查询表
ServiceDiscoveryServerServiceClassID_UUID = '{00001000-0000-1000-8000-00805F9B34FB}' BrowseGroupDesc ...
- RCF
1. RCF: 纯c++的RPC, 不引入IDL, 大量用到boost,比较强大.2. casocklib: protobuf + asio 较完善实现3. eventrpc: protobuf + ...
- Resizing the View(待续。。。。)
在iOS开发的过程中,控件的大小和位置如何去安排是一个现在看来比较麻烦的事情,需要从上到下的通知和从下到上的调整.而这部分在整个开发过程中是比较重要的,但是却经常没有被掌握.如果将这部分掌握,不管界面 ...
- 两个有用的shell工具总结
shell工具之一:sed sed基础 sed编辑器被称作流编辑器,与常见的交互式文本编辑器刚好相反.文本编辑器可以通过键盘来交互式地插入.删除.替换文本中的数据:而流编辑器是基于一组预先的规则来编辑 ...
- 不再让内容把td撑开
<style type="text/css"> table {width:600px;table-layout:fixed;} td {white-space:nowr ...