关闭浏览器网页触发事件_浅析浏览器渲染和 script 加载
前言
前端代碼離不開瀏覽器環(huán)境,理解 js、css 代碼如何在瀏覽器中工作是非常重要的。
如何優(yōu)化渲染過(guò)程中的回流,重繪?script 腳本在頁(yè)面中是怎么個(gè)加載順序?了解這些對(duì)前端性能優(yōu)化起著非常大的作用。
借著這篇文章,讓自己對(duì)這塊知識(shí)的理解更深一步。
渲染
渲染樹(Render Tree)
瀏覽器通過(guò)解析 HTML 和 CSS 后,形成對(duì)應(yīng)的 DOM 樹和 CSSOM 樹。
從根節(jié)點(diǎn)開始解析 DOM 樹節(jié)點(diǎn)并匹配對(duì)應(yīng)的 CSSOM 樣式規(guī)則,選擇可見(jiàn)的的節(jié)點(diǎn),最終結(jié)合成一顆渲染樹。
從上圖能看到渲染樹的特點(diǎn):
- 渲染樹中不包含 head、script、link、meta 之類不可見(jiàn)的節(jié)點(diǎn)
- CSS 定義的樣式規(guī)則將和實(shí)際的 DOM 匹配,并且被 display:none 修飾的節(jié)點(diǎn)最終不會(huì)出現(xiàn)在渲染樹中
渲染階段
根據(jù)上圖,整個(gè)渲染階段分為三部分:
- 渲染樹的形成:通過(guò) DOM 和 CSSOM 形成渲染樹
- 布局 Layout(自動(dòng)重排 Reflow):基于頁(yè)面的流式布局,遍歷渲染樹節(jié)點(diǎn),不斷計(jì)算節(jié)點(diǎn)最終的位置,幾何信息,樣式等屬性后,輸出一個(gè)“盒模型”
- 繪制 Paint(柵格化):將節(jié)點(diǎn)位置,大小根據(jù)屏幕的窗口大小換算成真實(shí)的像素,同顏色等屬性一同“畫到”頁(yè)面上
回流和重繪
基本概念
- 回流 Reflow:某些元素位置、幾何形狀的更改需要瀏覽器重新計(jì)算相關(guān)元素。
- 重繪 Repaint:將回流重排好的元素繪制到頁(yè)面上,但也因某些 js、css 的修改導(dǎo)致渲染樹發(fā)生變化,瀏覽器需要再次繪制頁(yè)面。
兩者的關(guān)系:觸發(fā)回流一定會(huì)觸發(fā)重繪, 而觸發(fā)重繪卻不一定會(huì)觸發(fā)回流
下圖很形象的展示了 Mozilla 頁(yè)面的渲染過(guò)程。
觸發(fā)回流條件
- 首次布局渲染頁(yè)面
- 改變?yōu)g覽器窗口大小
- 改變字體
- 網(wǎng)頁(yè)內(nèi)容變化
- 觸發(fā) CSS 偽類
- 操作 DOM
- style 樣式表發(fā)生變化
- 調(diào)用 DOM 元素的 offsetXX, clientXX,scrollXX,getClientRects 等屬性方法,獲取元素當(dāng)前的位置度量信息(參見(jiàn))
如何測(cè)試網(wǎng)頁(yè)性能
都知道頻繁的渲染過(guò)程會(huì)影響網(wǎng)頁(yè)性能,但怎么知道網(wǎng)頁(yè)開始渲染內(nèi)容了呢?
我們可以通過(guò) Chrome 的 F12,選擇 Rendering 來(lái)查看網(wǎng)頁(yè)的性能。
- Paint flashing: 以綠色高亮重繪區(qū)域
- Layout Shift Regions: 以藍(lán)色高亮布局發(fā)生變化的區(qū)域
結(jié)合上面的方法,用 一個(gè)簡(jiǎn)單的 Demo 來(lái)示意:
能從圖中看到,這些操作 觸發(fā)了瀏覽器的重繪:
- 鼠標(biāo)移至按鈕上,觸發(fā)了默認(rèn)的 hover 效果(出現(xiàn)綠框)
- 改變?cè)?color 屬性(出現(xiàn)綠框)
- 修改元素 top 屬性,不斷改變?cè)匚恢糜绊懖季?出現(xiàn)綠框,藍(lán)框)
提升渲染性能
布局/回流 和 繪制/重繪 是頁(yè)面渲染必須會(huì)經(jīng)過(guò)的兩個(gè)過(guò)程,不斷觸發(fā)它們肯定會(huì)增加性能的消耗。
瀏覽器會(huì)對(duì)這些操作做優(yōu)化(把它們放到一個(gè)隊(duì)列,批量進(jìn)行操作),但如果我們調(diào)用上面提到的 offsetXX, clientXX,scrollXX,getClientRects 等屬性方法就會(huì)強(qiáng)制刷新這個(gè)隊(duì)列,導(dǎo)致這些隊(duì)列批量?jī)?yōu)化無(wú)效。
下面列舉一些簡(jiǎn)單優(yōu)化方式:
- 不要使用 table 布局 table 布局會(huì)破壞 HTML 流式解析過(guò)程,甚至內(nèi)部元素改動(dòng)會(huì)觸發(fā)整個(gè) table 重繪
- 將需更改的 class 放到最里層 明確元素位置,減少父類元素不必要渲染判斷
- 使用 fixed、absolute 屬性修飾復(fù)雜多變的處理(動(dòng)畫) 將改變范圍降到最低程度,避免影響到父級(jí)元素
- 合并,減少 DOM 操作;通過(guò)虛擬 DOM 來(lái)代替
腳本的加載
link 和 script 加載文件的差異
注:均放在 head 標(biāo)簽內(nèi)。
考個(gè)問(wèn)題:CSS 定義在 head 中,其需加載 5 秒,請(qǐng)問(wèn)頁(yè)面加載后內(nèi)容會(huì)先優(yōu)先展示嗎?
我被渲染出來(lái)了我原先以為頁(yè)面內(nèi)容會(huì)優(yōu)先渲染,CSS 加載完成后才改變內(nèi)容樣式。其實(shí)這是錯(cuò)的。
從上圖看到,頁(yè)面加載后,body 內(nèi)元素就已經(jīng)解析好了,只是沒(méi)有渲染到頁(yè)面上。隨后 CSS 文件加載后,帶有樣色的內(nèi)容才被渲染到頁(yè)面上。
延遲的 link 的加載阻斷了頁(yè)面渲染,但并沒(méi)有影響 HTML 的解析,當(dāng) CSS 加載后,DOM 完成解析,CSSOM 和 DOM 形成渲染樹,最后將內(nèi)容渲染到頁(yè)面上。
反問(wèn),將 link 替換成 script 效果也一樣嗎?
與 link 不同,script 的加載會(huì)阻斷頁(yè)面 HTML 的解析,瀏覽器解析完 script 后,會(huì)等待 js 文件加載完后,頁(yè)面才開始后續(xù)的解析,body 內(nèi)容才出現(xiàn)。
head 和 body 中的 script 標(biāo)簽
學(xué)前端時(shí)相信都聽過(guò)這樣的名言:
CSS 寫在 head 里,js 寫在 body 結(jié)束標(biāo)簽前
知道了上面 link 和 script 的區(qū)別后,應(yīng)該明白前半句的含義,下面來(lái)解釋下后半句。
下面 script 均在 body 中。
頁(yè)面渲染 和 script 加載
先看下腳本在 body 中的一般情況:
在 body 內(nèi)部的首位分別加載兩個(gè) js 文件,前者延遲 3 秒,后者延遲 5 秒,為了清楚他們的“工作”情況,在 head 中添加了定時(shí)器示意。
我被渲染了能看到 body 中定義的內(nèi)聯(lián)腳本首先工作,初始化 foo 變量。
隨后加載 addTen.js,并阻斷頁(yè)面渲染。3 秒后,輸出 js 內(nèi)容(foo 賦值為 10),頁(yè)面并重新開始解析,展示 div 內(nèi)容。
最后加載 addOne.js ,繼續(xù)等待 2 秒后,輸出 js 內(nèi)容(foo 賦值為 11)。
多個(gè) script 文件的加載
如果前一個(gè) js 文件加載慢于后一個(gè),會(huì)有怎么個(gè)效果?
我被渲染了兩個(gè) script 標(biāo)簽并行加載,1 秒后 addOne.js 首先加載完畢,等待 4s 秒后,addTen.js 加載完后,頁(yè)面直接渲染(因?yàn)?script 已經(jīng)全部完成)。
簡(jiǎn)單總結(jié)下
所以建議 script 放在 body 結(jié)束標(biāo)簽之前,確保頁(yè)面內(nèi)容全部解析完成并開始渲染。
DOM 的 DOMContentLoaded 事件
DOMContentLoaded 事件可以來(lái)確定整個(gè) DOM 是否全部加載完成,下面我們簡(jiǎn)單測(cè)試下:
我被渲染了最終輸出:
addTen.jsfoo 10addOne.jsfoo 11[ready] documentDOMContentLoaded 事件的定義是異步回調(diào)方式,當(dāng) DOM 加載完成后觸發(fā),即使寫在最前面,也會(huì)等待后面的 script 加載完成后才觸發(fā)。
這里順便提個(gè) window.onload :
window.onload 和 DOMContentLoaded 不同,前者會(huì)等待頁(yè)面中所有的資源加載完畢后再調(diào)用執(zhí)行(比如:img 標(biāo)簽),后者在 DOM 加載完畢后即觸發(fā)。
“真正的異步腳本”——?jiǎng)討B(tài)腳本
能看到無(wú)論 script 放在那個(gè)位置,瀏覽器都會(huì)等待他們直至 body 內(nèi)的文件全部加載完。
那有什么 真正的異步 腳本加載嗎?(不會(huì)阻斷頁(yè)面解析)
那就是 動(dòng)態(tài)腳本。
如果你接觸過(guò)第三方網(wǎng)頁(yè)統(tǒng)計(jì)腳本,那將比較了解,下面給段示例代碼:
我被渲染了最終輸出:
addTen.jsafoo 10addOne.jsfoo 11[ready] document已加載 5 秒已加載 6 秒已加載 7 秒已加載 8 秒dynamicScript.js is runningdynamicScript.js loaded已加載 9 秒已加載 10 秒定義了需要加載 8 秒的 dynamicScript.js 文件,所有的 script 加載方式依舊異步,但 dynamicScript.js 在 DOMContentLoaded 觸發(fā)后,最后才執(zhí)行,瀏覽器并沒(méi)有等待它的加載完成后才渲染頁(yè)面。
我們也可以將它放在 head 中。這種通過(guò)腳本來(lái)動(dòng)態(tài)修改 DOM 結(jié)構(gòu)的加載方式是 無(wú)阻塞式 的,不受其他腳本加載的影響。
defer 和 async
我們可以在 script 定義 defer 、 async ,使整個(gè)腳本加載方式更加友好。比如:被修飾的腳本在 head 中,將不會(huì)阻斷 body 內(nèi)容的展示。
注意: defer 修飾的腳本將延遲到 body 中所有定義的腳本之后,DOM(頁(yè)面內(nèi)容)加載完之前觸發(fā); async 不會(huì)像 defer 一樣等待 body 中的腳本,而是當(dāng)前腳本一加載完畢就觸發(fā)。
我被渲染了加載順序:
已加載 1 秒已加載 2 秒scriptAsync.js已加載 3 秒已加載 4 秒addTen.jsfoo 10addOne.jsfoo 11scriptDefer.js[ready] document已加載 5 秒已加載 6 秒已加載 7 秒已加載 8 秒dynamicScript.js is runningdynamicScript.js loaded已加載 9 秒已加載 10 秒本文使用 mdnice 排版
總結(jié)
以上是生活随笔為你收集整理的关闭浏览器网页触发事件_浅析浏览器渲染和 script 加载的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python 人脸关键点检测_openc
- 下一篇: html浏览器的区别是什么意思,不同浏览