html页面加载完成后会触发的事件_前端隐秘角落 - 页面渲染
前言
如圖所示,webkit內核瀏覽器的渲染過程(解析HTML,構建DOM樹,解析CSS,構建CSSOM樹 ,構建render樹,布局layout,繪制painting),這些過程理解起來可能有些抽象,今天我們一起通過chrome開發者工具來直觀的理解一下瀏覽器渲染頁面的過程。
頁面渲染過程
Performance工具
我們將通過Performance工具來分析頁面渲染過程,首先按Command+ Option+ I(Mac)或 Control+ Shift+ I(Windows,Linux)打開DevTools,然后打開performance工具的界面。
我們可以看到Performance 的默認引導頁面:
- 第一句提示語所對應的操作是立即開始記錄當前頁面發生的所有事件,點擊停止按鈕才會停止記錄。
- 第二句對應的操作則是重載頁面并記錄事件,工具會自動在頁面加載完畢處于可交互狀態時停止記錄。
兩者最終都會生成報告(生成報告需要大量運算,會花費一些時間)。我們以實際前端項目(vue項目)為例,點刷新按鈕重載頁面并記錄事件,得到如下報告。
網絡請求
這里提到網絡請求,是因為瀏覽器渲染頁面離不開網絡請求,瀏覽器渲染進程開始接收HTML數據的標志,其實是收到了瀏覽器的導航的確認信息,瀏覽器的導航過程主要是在網絡層,涉及DNS解析,瀏覽器強緩存/協商緩存,TCP三次握手建立連接,http請求響應,TCP四次揮手斷開連接等多個環節,這里可以借助performance工具來理解瀏覽器渲染頁面之前的網絡請求部分:
上圖中,可看到Network模塊中有不同的顏色請求,它們分別代表的是藍色-HTML、黃色-JS、綠色-圖片的資源請求,如果有CSS文件的請求的話,它會被標識為紫色。另外一個細節點是,圖中每個資源請求的左上角都有一個小方塊,深藍色的小方塊代表較高優先級的請求,淺藍色則代表較低優先級,很明顯優先請求的資源(html、webpack打包的manifest、vendor、app主文件請求)都標識了深藍色的小方塊。
我們再來看Event log中瀏覽器的活動:
- 請求html
- 接收響應頭
- 瀏覽器接收到文檔,開始解析處理
- html響應數據已被接收
- 網絡請求完成
另外,圖中可看到在瀏覽器發送請求之前,還做了一系列的事情,這是因為我們上面選擇重載頁面記錄事件,所以在send requerst請求Html之前,會觸發瀏覽器一系列默認事件行為:webkitvisibilitychange,unloadEventStart,unloadEventEnd等。
Parse HTML
在瀏覽器渲染引擎內部,有一個叫HTML 解析器(HTMLParser)的模塊,它負責將HTML字節流轉換為DOM結構。HTML Standard規范定義了瀏覽器渲染HTML為DOM的方法。
- HTML解析器并不是等整個文檔加載完成之后再解析的,而是網絡進程加載了多少數據,HTML解析器就解析多少數據。
瀏覽器收到響應之后,我們來看下瀏覽器拿到的html文本
<!DOCTYPE html> <html><head><meta charset=utf-8><title></title><link rel=dns-prefetch href=//s1.zhuanstatic.com>...<meta name=description content=轉轉><meta name=viewport content="width=device-width,viewport-fit=cover,initial-scale=1,maximum-scale=1,user-scalable=no"><meta content="telephone=no,email=no" name=format-detection><meta name=apple-mobile-web-app-capable content=yes><meta name=apple-mobile-web-app-status-bar-style content=default> </head><body><div id=app></div><script type=text/javascriptsrc=https://s1.zhuanstatic.com/u/bmmain/static/js/manifest.f741ad8a3e48f84ad413.js></script><script type=text/javascriptsrc=https://s1.zhuanstatic.com/u/bmmain/static/js/vendor.6858ed31f5c34c2f6f8e.js></script><script type=text/javascript src=https://s1.zhuanstatic.com/u/bmmain/static/js/app.9ff176b78d7af021c2b0.js></script> </body></html>再來觀察Event Log中瀏覽器第一次解析HTML的活動:解析html文本,遇到<body>中的<script>標簽,發送了三個請求,分別請求manifest.js,vendor.js,app.js文件
JS的下載與執行
上圖顯示,解析HTML的過程中遇到 <script> 標簽時,渲染進程會停止解析HTML,而去加載,解析和執行js代碼,當腳本執行完成之后,HTML解析器才會恢復解析過程,而js外鏈資源的加載,解析和執行通常又會很耗時,這就是前端常提及到的js阻塞的原因。當然,瀏覽器設置停止解析HTML的機制也是有原因的,因為js可能會改變DOM的結構(例如使用document.write等API)。不過我們也有多種方式來告知瀏覽器應對如何應對某個資源,例如在<script> 標簽上添加了 async 或 defer 等屬性,瀏覽器會異步的加載和執行JS代碼,而不會阻塞渲染。
加載次優先級的資源
html頁面中有CSS,JS,字體等額外的資源,這些資源也需要從網絡上或者瀏覽器緩存中獲取。主進程可以在構建 DOM 的過程中會逐一請求它們,為了加速,瀏覽器的預加載掃描器會同時運行,如果在 html 中存在 <img> <link> 等標簽,預加載掃描器會把這些請求傳遞給瀏覽器進程中的網絡線程進行相關資源的下載。
構建DOM樹
為什么瀏覽器要構建DOM樹?這是因為瀏覽器無法直接理解和使用HTML文本,需要將HTML轉換為瀏覽器能夠理解的結構—DOM樹。為了更加直觀地理解DOM樹,我們可以打開Chrome的“開發者工具”,選擇“Console”標簽來打開控制臺,然后在控制臺里面輸入“document”后回車,就能看到一個完整的DOM樹結構,如下圖所示:
DOM和HTML內容幾乎是一樣的,但是和HTML不同的是,DOM是保存在內存中樹狀結構,可以通過JavaScript來查詢或修改其內容。
構建CSSOM
為什么瀏覽器要構建CSSOM樹?原因和瀏覽器構建DOM樹一樣,也是無法直接理解這些純文本的CSS樣式,所以當渲染引擎接收到CSS文本時,會執行一個轉換操作,將CSS文本轉換為瀏覽器可以理解的結構——styleSheets。我們可以在Chrome控制臺中查看其結構,只需要在控制臺中輸入document.styleSheets,然后就看到如下圖所示的結構
樣式計算
渲染進程主線程計算每一個元素節點的最終樣式值,即使沒有任何CSS樣式,瀏覽器對每個元素也會有一個默認的樣式。我們也可以通過Chrome查看瀏覽器計算后的樣式
生成布局樹
為了構建布局樹Render tree,瀏覽器大體上完成了下面這些工作
- 遍歷DOM樹中的所有可見節點,并把這些節點加到布局(Layout)中;
- 而不可見的節點會被布局樹忽略掉,如head標簽下面的全部內容,再比如body.p.span這個元素,因為它的屬性包含 dispaly:none,所以這個元素也沒有被包進布局樹。
回到Event Log繼續觀察分析,如下圖,js引擎在徹底加載執行完畢vendor.js,manifest.js,app.js后,渲染引擎回歸,開始第三次解析Html,開始進行首次樣式計算和布局,并動態加載manifest映射中的app-async.js等(項目webpack打包split chunk優化后的產物)和首頁correlate-Hplan.js資源等。
繪制
即使知道了不同元素的位置及樣式信息,瀏覽器還需要知道不同元素的繪制先后順序才能正確繪制出整個頁面。在繪制階段,主線程會遍歷布局樹創建待繪制列表。打開“開發者工具”的“Layers”標簽,選擇“document”層,來實際體驗下繪制列表
繪制過程可以將布局樹中的元素分解為多個層。
回到performance面板,我們勾選上控制面板里的 Enable advanced paint instrumentation 記錄渲染事件的細節:選擇Frames中的一塊,可在“Event log” 選項卡旁邊的新“Layers”選項卡中顯示有關頁面分層的信息。
將內容提升到GPU上的層(而不是CPU上的主線程)可以提高繪制和重新繪制性能。有一些特定的屬性和元素可以實例化一個層,包括<video>和<canvas>,任何CSS屬性為opacity、3D轉換、will-change的元素,還有一些其他元素。這些節點將與子節點一起繪制到它們自己的層上,除非子節點由于上述一個(或多個)原因需要自己的層。層確實可以提高性能,但是它以內存管理為代價,因此不應作為web性能優化策略的一部分過度使用。
繼續來觀察這個階段的Event Log的活動,剛剛分析到瀏覽器第三次解析html,動態加載完webpack拆分的chunk和首頁js,js引擎執行完畢觸發Event load事件,緊接著觸發了domInteractive事件也標識了html解析結束,html解析器結束工作后觸發docmument.readystatechange事件,緊接著觸發DomContentLoaded事件后,更新布局樹,進行首次繪制。
這里我們再來理解下這個DomContentLoaded事件與Load事件的區別
HTML 頁面的生命周期包含三個重要事件:- DOMContentLoaded —— 瀏覽器已完全加載 HTML,并構建了 DOM 樹,但像 和樣式表之類的外部資源可能尚未加載完成。
- load —— 瀏覽器不僅加載完成了 HTML,還加載完成了所有外部資源:圖片,樣式等。
- beforeunload/unload —— 當用戶正在離開頁面時。
我們來看Timings模塊,可以看到DCL,FP,FCP,L,LCP幾個重要的與頁面性能優化密切相關的事件縮寫,這里我們只理解下DCL(DOMContentLoaded)和L(load),load事件觸發的時機明顯晚于DOMContentLoaded
合成與顯示
一旦層樹被創建,繪制列表被確定,主線程會把這些信息通知給合成器線程,合成器線程會柵格化每一層。有的層的可以達到整個頁面的大小,因此,合成器線程將它們分成多個磁貼,并將每個磁貼發送到柵格線程,柵格線程會柵格化每一個磁貼并存儲在 GPU 顯存中。合成線程發送繪制圖塊命令DrawQuad給瀏覽器進程。瀏覽器進程根據DrawQuad消息生成頁面,并顯示到顯示器上。
回到performance工具,這里,我們滑動縮小概覽模塊選中的區間,定位到頁面骨架屏與初次渲染出頁面的中間,在Event Log中只勾選Painting會自動篩選出區間范圍內的繪制事件日志,可以看到瀏覽器進行了多次繪制和合成圖層的操作。
待優化的性能問題
額,這部分本來沒有在計劃范圍內 ,主要是兩個問題被chrome??了:
- Long task多個主任務都花費了較長的時間被警告,主要是打包后js的體積大造成的
- Layout Shift布局偏移過大,導致用戶體驗差,簡單點理解就是你準備點一個鏈接或者按鈕,但就在你手指觸摸到屏幕的前一秒,鏈接移動了,你點到了其他地方
總結
通過performance工具的Event Log模塊,分析了一下轉轉某實際vue項目 SPA單頁應用頁面在瀏覽器中的渲染過程,過程中可能有理解不到位的地方,歡迎多多指出呀。
參考文獻
[1].https://blog.poetries.top/browser-working-principle/guide/part1/lesson05.html
[2].https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/reference
[3].https://developer.mozilla.org/zh-CN/docs/Web/Performance/%E6%B5%8F%E8%A7%88%E5%99%A8%E6%B8%B2%E6%9F%93%E9%A1%B5%E9%9D%A2%E7%9A%84%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86
[4].https://developers.google.com/web/updates/2018/09/inside-browser-part3
[5].https://zhuanlan.zhihu.com/p/41017888
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的html页面加载完成后会触发的事件_前端隐秘角落 - 页面渲染的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 喵喵记账怎么出小票
- 下一篇: 如何设置 iPhone 备忘录格式