Chrome 瀏覽器有意改變<link rel="stylesheet">
的加載方式,當(dāng)其出現(xiàn)在<body>
中時(shí),這一變化將更加明顯。筆者決定在本文中進(jìn)行詳細(xì)說明這種改變可能帶來影響與好處梆掸。
一.目前CSS文件的加載方式
<head>
<link rel="stylesheet" href="/all-of-my-styles.css">
</head>
<body>
…content…
</body>
CSS 會(huì)阻礙渲染,因此在all-of-my-styles.css
全部加載完之前酿愧,用戶就只能面對一片空白的屏幕沥潭。
通常,我們將某個(gè)站點(diǎn)的所有 CSS 樣式合并為一到兩個(gè)資源嬉挡,這意味著用戶會(huì)下載一堆當(dāng)前頁面根本就用不上的規(guī)則钝鸽。這是因?yàn)榫W(wǎng)站可能包含許多不同類型的頁面,每個(gè)頁面都有自己的「組件」庞钢;而在組件級別傳遞 CSS 的話拔恰,會(huì)降低 HTTP/1 的性能。
然而基括,對 SPDY 和 HTTP/2 來說颜懊,事實(shí)卻并非如此。在這些協(xié)議中,許多小資源只需要很小的代價(jià)就能完成遞送河爹,并且被獨(dú)立緩存匠璧。
<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>
時(shí)頁面將包含的內(nèi)容咸这,從而防止 streaming夷恍。與此同時(shí),瀏覽器還是只能等待所有 CSS 樣式加載完畢媳维,才能開始渲染酿雪。如果加載 /site-footer.css
的速度不夠快,就會(huì)耽誤所有頁面的渲染侄刽。
二.目前最先進(jìn)的 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>
在上面的代碼中指黎,通過一些內(nèi)聯(lián)樣式我們可以加速初始渲染,同時(shí)隱藏起還沒有加載完樣式的組件州丹,并通過 JavaScript 異步地完成加載醋安。剩余的 CSS 加載完后會(huì)重寫.main-article
中的display:none
。
這個(gè)方法受到性能專家的推崇,他們認(rèn)為這是快速完成初始渲染的好方法墓毒,并且經(jīng)過實(shí)地測量確實(shí)在加載的時(shí)候快了不少茬故。
但也存在一些不足之處。蚁鳖。。赁炎。醉箕。。
「1.它需要一個(gè)(小的)JavaScript 庫」
這是由 WebKit 的實(shí)現(xiàn)方式造成的徙垫。一旦頁面中添加了<link rel="stylesheet">
讥裤,即使樣式表是由 JavaScript 加載的,WebKit 還是會(huì)在加載完成之前阻礙渲染姻报。
在 Firefox 和 IE/Edge 瀏覽器中己英,通過 JS 加載樣式表是完全異步進(jìn)行的。穩(wěn)定版本的 Chrome 瀏覽器是通過 WebKit 的方式加載的吴旋,但在 Canary 版本中损肛,仍然是使用 Firefox/Edge 加載方式。
「2.必須經(jīng)歷兩個(gè)加載階段」
在上述模式中荣瑟,內(nèi)聯(lián)的 CSS 通過display:none
隱藏了沒有加載完樣式的內(nèi)容治拿,直到異步加載完剩余的 CSS 樣式。如果你將這些樣式分派到兩個(gè)或多個(gè) CSS 文件中笆焰,這些文件有可能不按照順序加載劫谅,導(dǎo)致加載過程中出現(xiàn)內(nèi)容錯(cuò)亂:
內(nèi)容錯(cuò)亂,就好比彈出廣告一樣,會(huì)導(dǎo)致用戶體驗(yàn)挫敗捏检,必須全力消滅荞驴。
既然有兩個(gè)加載階段,你就必須決定渲染的先后順序贯城。你當(dāng)然會(huì)想首先渲染「位置顯要」的內(nèi)容熊楼。但是,所謂的「位置」是根據(jù)窗口大小來決定的冤狡。因此孙蒙,問題來了,你得找出一把「萬能」鑰匙悲雳。
三.一個(gè)更簡單挎峦、更好的方法
<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>
計(jì)劃是這樣的:針對每個(gè)<link rel="stylesheet">
,加載樣式表時(shí)我們阻止渲染它的后續(xù)內(nèi)容合瓢,但是允許渲染它之前的內(nèi)容坦胶。樣式表是并行加載的,但是按照一定的順序顯示晴楔。這使得<link rel="stylesheet">
的效用與<script src="…"></script>
相近顿苇。
假設(shè)網(wǎng)站 header、正文和 footer 的 CSS 已經(jīng)加載完畢税弃,但其余內(nèi)容仍在等待纪岁,那么頁面會(huì)是這樣的:
- Header:已渲染
- 正文:已渲染
- 評論部分:未渲染,它前面的 CSS 還未被加載(
/comment.css
)则果。 - 關(guān)于本站:未渲染幔翰。它前面的 CSS 還未被加載(
/comment.css
)。 - Footer:未渲染西壮。盡管它本身的 CSS 已加載完成遗增,但它前面的 CSS 還未被加載(
/comment.css
)。
這是一個(gè)按順序渲染的頁面款青。你不需要決定哪部分內(nèi)容在「顯要位置」做修,只要在頁面組件第一次實(shí)例化之前引入該組件的 CSS 即可。它完全兼容 Streaming抡草,因?yàn)槌悄阈枰渭埃駝t不必要輸出<link>
。
當(dāng)使用內(nèi)容決定布局的布局系統(tǒng)時(shí)(例如表格和 flexbox)康震,要注意避免加載時(shí)出現(xiàn)內(nèi)容錯(cuò)位旋炒。這不是什么新問題了,但是分步渲染會(huì)使得它出現(xiàn)得更為頻繁签杈。你可以通過 hack flexbox 來解決瘫镇,但對整體頁面布局來說鼎兽,使用 CSS grid 工具效果更佳(不過對小一些的組件來說,flexbox 還是很棒的)铣除。
四.Chrome瀏覽器的改變
HTML 規(guī)范并沒有規(guī)定 CSS 應(yīng)當(dāng)怎樣阻止頁面渲染谚咬,它不鼓勵(lì)在 body 中使用<link rel="stylesheet">
,但是所有的瀏覽器都允許使用尚粘。當(dāng)然了择卦,瀏覽器們在處理 body 中的 link 時(shí)都有自己的方法:
Chrome和Safari:一旦發(fā)現(xiàn)
<link rel="stylesheet">
就停止渲染,并且在已發(fā)現(xiàn)的樣式表全部完成加載之前不會(huì)開始渲染郎嫁。這會(huì)導(dǎo)致<link>
前未被渲染的內(nèi)容也被阻塞秉继。Firefox: head中的
<link rel="stylesheet">
會(huì)阻塞渲染,直至所有已發(fā)現(xiàn)的樣式表加載完畢泽铛,body中的<link rel="stylesheet">
并不阻塞任何渲染尚辑,除非某個(gè) head 中的樣式表已經(jīng)阻塞了渲染,這會(huì)導(dǎo)致無樣式的內(nèi)容出現(xiàn)閃爍(FOUC)盔腔。IE/Edge: 阻塞解析器直到樣式表加載完畢杠茬,但是允許渲染
<link>
之前的內(nèi)容。
在 Chrome 團(tuán)隊(duì)弛随,我們喜歡 IE/Edge 的方式瓢喉,所以打算跟它看齊。這就允許上文描述的漸進(jìn)式 CSS 渲染方式舀透。我們正在努力把它變成標(biāo)準(zhǔn)栓票,從允許<body>
中的<link>
開始。
目前 Chrome/Safari 采用的方式是向下兼容的愕够,帶來的問題是阻塞渲染的時(shí)間比實(shí)際需要的長逗载。Firefox 的方式稍微復(fù)雜一些某弦,但有個(gè)解決的方法:
「Firefixing!」
因?yàn)?Firefox 并不總是為了<body>
中的<link>
阻塞渲染努隙,我們得為這個(gè)多花點(diǎn)功夫來避免 FOUC蠢笋。謝天謝地這很容易,因?yàn)?code><script>會(huì)阻塞解析端盆,同時(shí)也會(huì)等掛起的樣式表完成加載:
<link rel="stylesheet" href="/article.css"><script> </script>
<main>…</main>
此處的<script>
元素必須是非空的,但加個(gè)空格足矣。
Firefox 和 Edge/IE 可以實(shí)現(xiàn)很美好的漸進(jìn)式渲染漩勤,而 Chrome 和 Safari 在所有 CSS 加載完畢之前只能給你看一張白屏。目前 Chrome/Safari 采用的方式怎么都比將所有的樣式表都放<head>
里要強(qiáng)缩搅,所以你現(xiàn)在就可以開始采用這個(gè)方法了越败。后面幾個(gè)月,Chrome 會(huì)遷移到 Edge 的模式硼瓣,這樣用戶就能體驗(yàn)更快的渲染速度了究飞。
就是這樣置谦!通過更簡單的方法加載你需要的 CSS,強(qiáng)力提升渲染速度亿傅。
五.快速定位 CSS 加載問題
那么問題來了媒峡,怎么樣才能知道是不是 css 加載影響了頁面的性能呢?只有定位到問題確實(shí)是 css 葵擎,老板才會(huì)給你時(shí)間和人力來優(yōu)化這方面的問題對不對谅阿?
筆者之前做過前端優(yōu)化的工作,國內(nèi)外的前端性能優(yōu)化工具也使用了不少酬滤,現(xiàn)階段可以較好實(shí)現(xiàn)這個(gè)定位頁面慢加載因素的工具有:
OneAPM Browser Insight签餐、AppDynamics、Ruxit盯串,大家有興趣的話可以去嘗試下氯檐。
注:本文原文作者為 Jake Archibald,由 OneAPM 運(yùn)營人員翻譯整理
原文地址:https://jakearchibald.com/2016/link-in-body/
Browser Insight 是一個(gè)基于真實(shí)用戶的 Web 前端性能監(jiān)控平臺(tái)嘴脾,能夠幫大家定位網(wǎng)站性能瓶頸男摧,網(wǎng)站加速效果可視化;支持瀏覽器译打、微信耗拓、App 瀏覽 HTML 和 HTML5 頁面。想閱讀更多技術(shù)文章奏司,請?jiān)L問 OneAPM 官方技術(shù)博客乔询。
本文轉(zhuǎn)自 OneAPM 官方博客