閱讀506 返回首頁    go 阿裏雲 go 技術社區[雲棲]


前端魔法堂:解秘FOUC

前言

 對於問題多多的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 >Hi</div>
  </body>
</html>

示例2:阻塞渲染

<html>
  <body>
    <script>
            // 打印出 <div >Hi</div>
      console.log(document.getElementById('hi'))
    </script>
    <link rel="stylesheet" href="./longtime.css">
    <div >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 ></script>
  </head>
  <body>
    <div >Hi</div>
  </body>
</html>

示例4:阻塞渲染

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

對於IE9

示例1:

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

示例2:

<html>
  <body>
        <!-- div#hi渲染了,加入到DOM Tree中 -->
    <style>#hi{color:red;}</style>
    <div >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 >
    <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的應用僅僅采用上述的方案效果並不理想。後續待我研究好後再追加一篇吧^_^
 尊重原創,轉載請注明來自:https://www.cnblogs.com/fsjohnhuang/p/6739064.html ^_^肥仔John

感謝

Flash of unstyled content
The FOUC Problem
Critical rendering path

最後更新:2017-05-05 11:31:39

  上一篇:go WebComponent魔法堂:深究Custom Element 之 從過去看現在
  下一篇:go 大數據開發—常見問題(2)