CSS 加載新方式

yuanlinchao 8年前發布 | 13K 次閱讀 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 InsightAppDynamicsRuxit ,大家有興趣的話可以去嘗試下。

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

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

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

來自: http://news.oneapm.com/bi-css/

 本文由用戶 yuanlinchao 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!