CSS 加載新方式

Chrome 瀏覽器有意改變<link rel="stylesheet">的加載方式,當其出如今<body>中時,這一變化將更加明顯。筆者決定在本文中進行詳細說明這種改變可能帶來影響與好處。css

#####一.目前CSS文件的加載方式html

<head>
  <link rel="stylesheet" href="/all-of-my-styles.css">
</head>
<body>
  …content…
</body>

CSS 會阻礙渲染,所以在all-of-my-styles.css所有加載完以前,用戶就只能面對一片空白的屏幕。前端

一般,咱們將某個站點的全部 CSS 樣式合併爲一到兩個資源,這意味着用戶會下載一堆當前頁面根本就用不上的規則。這是由於網站可能包含許多不一樣類型的頁面,每一個頁面都有本身的「組件」;而在組件級別傳遞 CSS 的話,會下降 HTTP/1 的性能。git

然而,對 SPDY 和 HTTP/2 來講,事實卻並不是如此。在這些協議中,許多小資源只須要很小的代價就能完成遞送,而且被獨立緩存。github

<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性能優化

這個方法受到性能專家的推崇,他們認爲這是快速完成初始渲染的好方法,而且通過實地測量確實在加載的時候快了很多。微信

但也存在一些不足之處。。。。。。app

「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 ,老闆纔會給你時間和人力來優化這方面的問題對不對?

網站性能優化— WebP 全方位介紹

筆者以前作過前端優化的工做,國內外的前端性能優化工具也使用了很多,現階段能夠較好實現這個定位頁面慢加載因素的工具備: OneAPM Browser InsightAppDynamicsRuxit,你們有興趣的話能夠去嘗試下。

注:本文原文做者爲 Jake Archibald,由 OneAPM 運營人員翻譯整理

原文地址:https://jakearchibald.com/2016/link-in-body/

Browser Insight 是一個基於真實用戶的 Web 前端性能監控平臺,可以幫你們定位網站性能瓶頸,網站加速效果可視化;支持瀏覽器、微信、App 瀏覽 HTML 和 HTML5 頁面。想閱讀更多技術文章,請訪問 OneAPM 官方技術博客
本文轉自 OneAPM 官方博客

相關文章
相關標籤/搜索