前言
對于問題多多的IE678,FOUC(flash of unstyled content)——瀏覽器樣式閃爍是一個不可忽視的話題,但對于ever green的瀏覽器就不用理會了嗎?下面嘗試較全面地解密FOUC。
到底什么是FOUC?
頁面加載解析時,頁面以樣式A渲染;當頁面加載解析完成后,頁面突然以樣式B渲染,導致出現頁面樣式閃爍。
樣式A,瀏覽器默認樣式 或 瀏覽器默認樣式 層疊 部分已加載的頁面樣式;
樣式B,瀏覽器默認樣式 疊加 全部頁面樣式。
為什么會出現FOUC
我們了解當輸入網址按回車后瀏覽器會向服務器發送請求,然后服務器返回頁面給瀏覽器,瀏覽器邊下載頁面邊解析邊渲染。
下面我們解剖一下邊下載頁面邊解析邊渲染的過程:
- 邊下載邊解析就是邊下載html邊構建DOM Tree;
- 瀏覽器以user agent stylesheet(瀏覽器內置樣式)為原料構建CSSOM Tree;
- DOM Tree+CSSOM Tree構建出Render Tree,然后頁面內容渲染出來;
- 當解析到inline stylesheet 或 internal stylesheet時,馬上刷新CSSOM Tree,CSSOM Tree或DOM Tree發生變化時會引起Render Tree變化;
- 當解析到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下載完成前(DOMContentLoaded后才渲染),整個頁面將不會被渲染。(估計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/fsjohnhuang/p/6739064.html ^^肥仔John
感謝
Flash of unstyled content
The FOUC Problem
Critical rendering path
文章列表