前端魔法堂:解祕FOUC

肥仔John發表於2019-02-16

前言

 對於問題多多的IE678,FOUC(flash of unstyled content)——瀏覽器樣式閃爍是一個不可忽視的話題,但對於ever green的瀏覽器就不用理會了嗎?下面嘗試較全面地解密FOUC。

到底什麼是FOUC?

 頁面載入解析時,頁面以樣式A渲染;當頁面載入解析完成後,頁面突然以樣式B渲染,導致出現頁面樣式閃爍。
 樣式A,瀏覽器預設樣式 或 瀏覽器預設樣式 層疊 部分已載入的頁面樣式;
 樣式B,瀏覽器預設樣式 疊加 全部頁面樣式。

為什麼會出現FOUC

 我們瞭解當輸入網址按回車後瀏覽器會向伺服器傳送請求,然後伺服器返回頁面給瀏覽器,瀏覽器邊下載頁面邊解析邊渲染。
下面我們解剖一下邊下載頁面邊解析邊渲染的過程:

  1. 邊下載邊解析就是邊下載html邊構建DOM Tree;

  2. 瀏覽器以user agent stylesheet(瀏覽器內建樣式)為原料構建CSSOM Tree;

  3. DOM Tree+CSSOM Tree構建出Render Tree,然後頁面內容渲染出來;

  4. 當解析到inline stylesheet 或 internal stylesheet時,馬上重新整理CSSOM Tree,CSSOM Tree或DOM Tree發生變化時會引起Render Tree變化;

  5. 當解析到external stylesheet時就先載入,然後如internal stylesheet那樣解析和重新整理CSSOM Tree和Render Tree了。
     上述步驟5中由於樣式檔案存在下載這個延時不確定的階段,因此網路環境不好或樣式資源體積大的情況下我們可以看到樣式閃爍明顯。

 這就是為什麼我們將external stylesheet的引入放在head標籤中的原因,在body渲染前先把相對完整的CSSOM Tree構建好。但大家都聽說過script會阻塞html頁面解析(block parsing),而link不會,那假如網路環境不好或樣式資源體積大時,body已經解析並加入到DOM Tree後,external stylesheet才載入完成,不是也會造成FOUC嗎?

style,link等樣式資源的下載、解析確實不會阻塞頁面的解析,但它們會阻塞頁面的渲染(block rendering)。

Block Parsing 和 Block Rendering的區別

Block Parsing: 阻塞HTML頁面解析,HTML頁面會被繼續下載,但阻塞點後面的標籤不會被解析,img,link等不會發請求獲取外部資源。
Block Rendering:阻塞HTML頁面渲染,HTML頁面會被繼續下載,阻塞點後面的標籤會繼續被解析,img,link等會繼續傳送請求獲取外部資源,但不會合成Rendering Tree或不會觸發頁面渲染,也不會執行JavaScript程式碼。
 各瀏覽器這方面還有一點差異:

對於Chrome

<link rel="stylesheet">,<link rel="import"> and @import url("<url>")會阻塞渲染。
示例1:阻塞解析

<html>
  <body>
    <script>
            // 列印出 null
      console.log(document.getElementById(`hi`))
    </script>
    <script src="./longtime.js"></script>
    <div id="hi">Hi</div>
  </body>
</html>

示例2:阻塞渲染

<html>
  <body>
    <script>
            // 列印出 <div id="hi">Hi</div>
      console.log(document.getElementById(`hi`))
    </script>
    <link rel="stylesheet" href="./longtime.css">
    <div id="hi">Hi</div>
  </body>
</html>

示例3:阻塞渲染

<html>
  <head>
    <script>
            // 列印出 hinull
      console.log(`hi` + document.getElementById(`hi`))
            // 列印出 hiscript#s
      console.log(`s` + document.getElementById(`s`))
    </script>
    <link rel="stylesheet" href="./longtime.css">
    <script id="s"></script>
  </head>
  <body>
    <div id="hi">Hi</div>
  </body>
</html>

示例4:阻塞渲染

<html>
  <body>
        <!-- div#hi在 ./longtime.css下載完前不會被渲染 -->
    <style>#hi{color:red;}</style>
    <link rel="stylesheet" href="./longtime.css">
    <div id="hi">Hi</div>
  </body>
</html>

示例2說明,如果阻塞渲染髮生在body標籤內,那麼body及其子元素會繼續解析並追加到DOM Tree中;
示例3說明,如果阻塞渲染髮生在head標籤內,那麼body及其子元素不會被追加到DOM Tree中。
示例4說明,不管external stylesheet在哪裡引入,在頁面的所有external stylesheets下載完成前,整個頁面將不會被渲染。(估計Chrome會預先統計external stylesheet的數量)

對於FireFox

示例1:阻塞渲染

<html>
  <body>
        <!-- div#hi的文字顯示為紅色,待./longtime.css下載完後又渲染為其他顏色 -->
    <style>#hi{color:red;}</style>
    <link rel="stylesheet" href="./longtime.css">
    <div id="hi">Hi</div>
  </body>
</html>

示例2:阻塞渲染

<html>
  <head>
        <!-- div#hi不顯示,直到./longtime.css下載完後 -->
    <style>#hi{color:red;}</style>
    <link rel="stylesheet" href="./longtime.css">
  </head>
  <body>
    <div id="hi">Hi</div>
  </body>
</html>

對於IE9

示例1:

<html>
  <body>
        <!-- div#hi沒有渲染,也沒有加入到DOM Tree中 -->
    <style>#hi{color:red;}</style>
    <link rel="stylesheet" href="./longtime.css">
    <div id="hi">Hi</div>
  </body>
</html>

示例2:

<html>
  <body>
        <!-- div#hi渲染了,加入到DOM Tree中 -->
    <style>#hi{color:red;}</style>
    <div id="hi">Hi</div>
    <link rel="stylesheet" href="./longtime.css">
  </body>
</html>

上面的示例表明,IE下block rendering等價於block parsing,因為連img,script,link,@import url()資源請求都會被阻塞。

解決方法

 現在我們知道FOUC時由於頁面採用臨時樣式來渲染頁面而導致的,其中僅有chrome能好的遮蔽了這一點,而其他瀏覽器就呵呵了。那有什麼方案可以解決呢?其實我們的目的就是不要讓使用者看到臨時樣式,那麼我們可以隱藏body,當樣式資源載入完成後再顯示body

<html class="no-js">
    <style>
        /*modernizr會將html的no-js替換為js,並將modernizr程式碼在最後時載入,那麼就能保證所有樣式檔案已經載入完成*/
        .no-js body{display: none!important;}
    </style>
    <body>
        <script src="modernizr.js"></script>
    </body>
</html>

(編譯modernizr時記得勾setClasses哦,否則不會替換no-js的!)

總結

 上述方案雖然解決了FOUC的問題,但很明顯地延長了首屏白屏時間,當前較流行的App Shell(可以理解為先顯示頁面佈局的骨架或一幅圖片)也會失效,所以對於2C的應用僅僅採用上述的方案效果並不理想。後續待我研究好後再追加一篇吧^_^
 尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohn… ^_^肥仔John

感謝

Flash of unstyled content
The FOUC Problem
Critical rendering path

相關文章