看完這兩篇:
- 【對瀏覽器首次渲染時間點的探究】https://github.com/jin5354/404forest/issues/73
- 【深入探究 eventloop 與瀏覽器渲染的時序問題】https://www.404forest.com/2017/07/18/how-javascript-actually-works-eventloop-and-uirendering/
- 【瀏覽器的工作原理:新式網絡瀏覽器幕后揭秘】https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
結論:
如果在 HTML 解析過程中,解析到了某個腳本,但這個腳本被 CSS 阻塞住了或者還沒下載完,則會中斷暫存當前的解析 task,繼續執行 eventloop,(參考下方給出的event loop的圖) 網頁被渲染。
如果 JS 全部是內聯的,或者網速好,在解析到</script>時腳本全都已下載完了,則解析 task 不會被中斷,也就不會出現渲染情況了。
這種特性可以用來做頁面骨架屏(解析中斷時提早渲染頁面)
先上一段代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<div id="app">
<p>俺是用來測試首屏渲染的文字。</p>
</div>
<script src="./bundle.js"></script>
</body>
</html>
-
問
<p>俺是用來測試首屏渲染的文字。<p>
會渲染嗎 - 在腳本被阻塞的時候,是會直接渲染出來的
『需要著重指出的是,這是一個漸進的過程。為達到更好的用戶體驗,呈現引擎會力求盡快將內容顯示在屏幕上。它不必等到整個 HTML 文檔解析完畢之后,就會開始構建呈現樹和設置布局。在不斷接收和處理來自網絡的其余內容的同時,呈現引擎會將部分內容解析并顯示出來。』 - 來自『瀏覽器的工作原理:新式網絡瀏覽器幕后揭秘』https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/
所以:html解析完畢之前,是可以進行繪制的,那么上述
<p>
標簽能渲染出來嗎?
首先 PerformancePaintTiming API
Paint Timing 提供的
PerformancePaintTiming
是一個提供頁面在構建過程中的“繪制”(也稱“渲染”)時間點信息的接口。“繪制”是指將渲染樹轉換為頁面上像素的過程。(可以獲取頁面繪制過程中相關事件(FP、FCP、FMP、TTI等)發生的時間)
----------------它的規范中說明:如果update the rendering
實例是first-paint (FP 首屏加載衡量指標之一)
那么就記錄時間戳,上報為first-paint
時間。如果update the rendering
實例是first-contentful-paint
那么就記錄時間戳,上報為first-contentful-paint (FCP 指標之一)
時間。
什么是update the rendering
?
-
先上圖
可以看到是event loop的最后一個階段
所以: 瀏覽器的首次渲染,只是老老實實的按照eventloop
來運行而已。eventloop
第一次進行到update the rendering
階段的時間點那就是first-paint
的時間點了
eventloop
是怎么運行的?
eventloop
按照task > microtask > render
的順序執行,所以可以看到,微隊列是先于頁面渲染運行的。對于task來說,Html解析其實就是一種典型的task
在 html parser
規范中檢索 eventloop
得:
(原文很晦澀,這里為了方便理解,直接翻譯最核心的幾句:)
當解析到
</script>
時:如果當前文檔存在阻礙 JS 執行的 CSS 或者當前的腳本 不處于
ready to be parser-executed
狀態,spin the event loop,直到不再存在阻礙 JS 執行的 CSS 且該段腳本處于ready to be parser-executed
。
我們已經知道 CSS 的加載是會阻礙 JS 執行的。而腳本不處于這個 ready to be parser-executed
狀態簡單理解就是還沒下載完。如果出現這兩種情況,腳本就無法立刻執行,需要等待。此時要進行 spin the eventloop,查閱規范,該操作即為:
(簡單翻譯)
- 暫存此時正在執行的 task 或 microtask
- 暫存此時的 js 執行上下文堆棧
- 清空 js執行上下文堆棧
- 如果當前正在執行的是 task,執行 microtask checkpoint
- 停止執行當前的 task/microtask。繼續執行 eventloop 的主流程。
- 當滿足條件時,重新添加之前暫存的 task/microtask,恢復暫存的 js 執行上下文堆棧,繼續執行。
簡單的說就是讓 eventloop
中斷并暫存當前正在執行的 task/microtask,保持 eventloop
的繼續執行,待一段時間之后滿足條件了再恢復之前的 task/microtask。
那么問題就水落石出了:
如果在 HTML 解析過程中,『解析到了某個腳本,但這個腳本被 CSS 阻塞住了或者還沒下載完』,則會中斷暫存當前的解析
task
,繼續執行eventloop
,網頁被渲染。
如果 JS 全部是內聯的,或者網速好,在解析到
</script>
時腳本全都已下載完了,則解析 task 不會被中斷,也就不會出現渲染情況了。