關閉瀏覽器網頁觸發事件_淺析瀏覽器渲染和 script 加載

5a2303deaad679ac776d9a811bb0df1b.png

前言

前端代碼離不開瀏覽器環境,理解 js、css 代碼如何在瀏覽器中工作是非常重要的。

如何優化渲染過程中的回流,重繪?script 腳本在頁面中是怎么個加載順序?了解這些對前端性能優化起著非常大的作用。

借著這篇文章,讓自己對這塊知識的理解更深一步。

渲染

渲染樹(Render Tree)

瀏覽器通過解析 HTML 和 CSS 后,形成對應的 DOM 樹和 CSSOM 樹。

從根節點開始解析 DOM 樹節點并匹配對應的 CSSOM 樣式規則,選擇可見的的節點,最終結合成一顆渲染樹

e6bc3e8f0a7ad266d6bd3f2b4280e86d.png

從上圖能看到渲染樹的特點:

  • 渲染樹中不包含 head、script、link、meta 之類不可見的節點
  • CSS 定義的樣式規則將和實際的 DOM 匹配,并且被 display:none 修飾的節點最終不會出現在渲染樹中

渲染階段

8dcc8d4bac16596dbf1ffffe8b76db42.png

根據上圖,整個渲染階段分為三部分:

  • 渲染樹的形成:通過 DOM 和 CSSOM 形成渲染樹
  • 布局 Layout(自動重排 Reflow):基于頁面的流式布局,遍歷渲染樹節點,不斷計算節點最終的位置,幾何信息,樣式等屬性后,輸出一個“盒模型”
  • 繪制 Paint(柵格化):將節點位置,大小根據屏幕的窗口大小換算成真實的像素,同顏色等屬性一同“畫到”頁面上

回流和重繪

基本概念

  • 回流 Reflow:某些元素位置、幾何形狀的更改需要瀏覽器重新計算相關元素。
  • 重繪 Repaint:將回流重排好的元素繪制到頁面上,但也因某些 js、css 的修改導致渲染樹發生變化,瀏覽器需要再次繪制頁面。

兩者的關系:觸發回流一定會觸發重繪, 而觸發重繪卻不一定會觸發回流

下圖很形象的展示了 Mozilla 頁面的渲染過程。

dcdfbb214ab35f3a554b5f94f476e408.gif

觸發回流條件

  • 首次布局渲染頁面
  • 改變瀏覽器窗口大小
  • 改變字體
  • 網頁內容變化
  • 觸發 CSS 偽類
  • 操作 DOM
  • style 樣式表發生變化
  • 調用 DOM 元素的 offsetXX, clientXX,scrollXX,getClientRects 等屬性方法,獲取元素當前的位置度量信息(參見)

如何測試網頁性能

都知道頻繁的渲染過程會影響網頁性能,但怎么知道網頁開始渲染內容了呢?

我們可以通過 Chrome 的 F12,選擇 Rendering 來查看網頁的性能。

cadb0ab9fbb76bd77ee345da1d603242.png
58007af69f8c0795b058735812c57f59.png
  • Paint flashing: 以綠色高亮重繪區域
  • Layout Shift Regions: 以藍色高亮布局發生變化的區域

結合上面的方法,用 一個簡單的 Demo 來示意:

5d102ca3e7b9669dd7786d3b2708c95e.gif

能從圖中看到,這些操作 觸發了瀏覽器的重繪

  • 鼠標移至按鈕上,觸發了默認的 hover 效果(出現綠框)
  • 改變元素 color 屬性(出現綠框)
  • 修改元素 top 屬性,不斷改變元素位置影響布局(出現綠框,藍框)

提升渲染性能

布局/回流繪制/重繪 是頁面渲染必須會經過的兩個過程,不斷觸發它們肯定會增加性能的消耗。

瀏覽器會對這些操作做優化(把它們放到一個隊列,批量進行操作),但如果我們調用上面提到的 offsetXX, clientXX,scrollXX,getClientRects 等屬性方法就會強制刷新這個隊列,導致這些隊列批量優化無效。

下面列舉一些簡單優化方式:

  • 不要使用 table 布局 table 布局會破壞 HTML 流式解析過程,甚至內部元素改動會觸發整個 table 重繪
  • 將需更改的 class 放到最里層 明確元素位置,減少父類元素不必要渲染判斷
  • 使用 fixed、absolute 屬性修飾復雜多變的處理(動畫) 將改變范圍降到最低程度,避免影響到父級元素
  • 合并,減少 DOM 操作;通過虛擬 DOM 來代替

腳本的加載

link 和 script 加載文件的差異

注:均放在 head 標簽內。

考個問題:CSS 定義在 head 中,其需加載 5 秒,請問頁面加載后內容會先優先展示嗎?

          
我被渲染出來了

我原先以為頁面內容會優先渲染,CSS 加載完成后才改變內容樣式。其實這是錯的。

ec655f5d353fcd6f94e72e9b5acc469a.gif

從上圖看到,頁面加載后,body 內元素就已經解析好了,只是沒有渲染到頁面上。隨后 CSS 文件加載后,帶有樣色的內容才被渲染到頁面上。

延遲的 link 的加載阻斷了頁面渲染,但并沒有影響 HTML 的解析,當 CSS 加載后,DOM 完成解析,CSSOM 和 DOM 形成渲染樹,最后將內容渲染到頁面上。

反問,將 link 替換成 script 效果也一樣嗎?

a2bfb539740d7610d9a68d6732147bd3.gif

與 link 不同,script 的加載會阻斷頁面 HTML 的解析,瀏覽器解析完 script 后,會等待 js 文件加載完后,頁面才開始后續的解析,body 內容才出現。

head 和 body 中的 script 標簽

學前端時相信都聽過這樣的名言:

CSS 寫在 head 里,js 寫在 body 結束標簽前

知道了上面 link 和 script 的區別后,應該明白前半句的含義,下面來解釋下后半句。

下面 script 均在 body 中

頁面渲染 和 script 加載

先看下腳本在 body 中的一般情況:

在 body 內部的首位分別加載兩個 js 文件,前者延遲 3 秒,后者延遲 5 秒,為了清楚他們的“工作”情況,在 head 中添加了定時器示意。

                      
我被渲染了
6767c0f66ed6c6812e9acee88fd5db4c.gif

能看到 body 中定義的內聯腳本首先工作,初始化 foo 變量。

隨后加載 addTen.js,并阻斷頁面渲染。3 秒后,輸出 js 內容(foo 賦值為 10),頁面并重新開始解析,展示 div 內容。

最后加載 addOne.js ,繼續等待 2 秒后,輸出 js 內容(foo 賦值為 11)。

340572169c396f7a46fd995cad51d650.png

多個 script 文件的加載

如果前一個 js 文件加載慢于后一個,會有怎么個效果?

我被渲染了

兩個 script 標簽并行加載,1 秒后 addOne.js 首先加載完畢,等待 4s 秒后,addTen.js 加載完后,頁面直接渲染(因為 script 已經全部完成)。

aabac8bbee4e39d478b8d8e230b46016.png

簡單總結下

  1. 無論在 head 還是 body 中,瀏覽器會等待 script 文件的加載(阻斷頁面解析渲染)
  2. 多個 script 的文件加載是異步的,不存在互相影響(后一個文件不需要等待前一個加載完后才下載),執行順序同定義順序

所以建議 script 放在 body 結束標簽之前,確保頁面內容全部解析完成并開始渲染。

DOM 的 DOMContentLoaded 事件

DOMContentLoaded 事件可以來確定整個 DOM 是否全部加載完成,下面我們簡單測試下:

我被渲染了

最終輸出:

addTen.jsfoo 10addOne.jsfoo 11[ready] document

DOMContentLoaded 事件的定義是異步回調方式,當 DOM 加載完成后觸發,即使寫在最前面,也會等待后面的 script 加載完成后才觸發。

這里順便提個 window.onload

window.onloadDOMContentLoaded 不同,前者會等待頁面中所有的資源加載完畢后再調用執行(比如:img 標簽),后者在 DOM 加載完畢后即觸發。

“真正的異步腳本”——動態腳本

能看到無論 script 放在那個位置,瀏覽器都會等待他們直至 body 內的文件全部加載完。

那有什么 真正的異步 腳本加載嗎?(不會阻斷頁面解析)

那就是 動態腳本

如果你接觸過第三方網頁統計腳本,那將比較了解,下面給段示例代碼:

我被渲染了

最終輸出:

addTen.jsafoo 10addOne.jsfoo 11[ready] document已加載  5  秒已加載  6  秒已加載  7  秒已加載  8  秒dynamicScript.js is runningdynamicScript.js loaded已加載  9  秒已加載  10  秒
668e619bffa63684a22df20150441af2.png

定義了需要加載 8 秒的 dynamicScript.js 文件,所有的 script 加載方式依舊異步,但 dynamicScript.js 在 DOMContentLoaded 觸發后,最后才執行,瀏覽器并沒有等待它的加載完成后才渲染頁面。

我們也可以將它放在 head 中。這種通過腳本來動態修改 DOM 結構的加載方式是 無阻塞式 的,不受其他腳本加載的影響。

defer 和 async

我們可以在 script 定義 deferasync ,使整個腳本加載方式更加友好。比如:被修飾的腳本在 head 中,將不會阻斷 body 內容的展示

注意: defer 修飾的腳本將延遲到 body 中所有定義的腳本之后,DOM(頁面內容)加載完之前觸發async 不會像 defer 一樣等待 body 中的腳本,而是當前腳本一加載完畢就觸發。

                  
我被渲染了

加載順序:

已加載  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 排版

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/455394.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/455394.shtml
英文地址,請注明出處:http://en.pswp.cn/news/455394.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Open vSwitch實驗常用命令

1. 基本架構 ovs-vsctl: 管理ovsdb-server的配置,提供OVSDB的配置方法,包括創建和刪除網橋、端口等; ovs-ofctl: 提供ovs-vswitchd的流表配置方法; ovs-dpctl: 配置OVS內核模塊,提供緩存流表的操作方法&#xff1b…

記IOS8中碰到的一個JS bug

IOS8的JS版本過低導致 var id "123"; var temp1 {id, "left": "200"}; // error in IOS8 var temp2 {"id":id, "left": "200"};平時還是多寫ES5的代碼,es6的語法總能碰到兼容的坑。 改了好幾天。…

Emmet的html語法

Emmet的html語法 所有操作按下“tab”鍵即可瞬間完成 元素 1.在編輯器中輸入元素名稱,即可自動補全生成 HTML 標簽,即使不是標準的 HTML 標簽。 2.輸入:! 或者 html:5 或者 html:4s 或者 html:4t 將自動補全html基本結構 嵌套操作 1.使用…

RTP Payload Format for H.264 Video

H.264 RTP協議的封裝格式rfc3984 英文原版:http://tools.ietf.org/html/rfc3984 部分中文翻譯: H.264 視頻 RTP 負載格式 1. 網絡抽象層單元類型 (NALU) NAL單元1字節包頭負載 NALU 頭由一個字節組成, 它的語法如下: —————|0|1|2|3|4|5|6|7|------…

js字符串、數組和數字常用方法總結

https://github.com/AnHyun/blog/issues/3 一、string 常用方法: 1.substring(start開始位置的索引,end結束位置索引) 截取的位置不包含結束位置的字符,只寫一個參數表示從開始位置截取到最后,輸入負值時將負值變為0,哪個較小作為開始位置 va…

Oracle 存儲過程錯誤之PLS-00201: 必須聲明標識符

轉自:http://blog.csdn.net/u010678947/article/details/20702149 錯誤: ORA-06550: 第 1 行, 第 7 列: PLS-00201: 必須聲明標識符ZUO.PROCE_TESTORA-06550: 第 1 行, 第 7 列: PL/SQL: Statement ignored 解決方法: (1&#x…

mysql中如何把兩個查詢結果列數不同并成一張表_MySQL

引言本文整理了MySQL相關的知識,方便以后查閱。 基礎架構下圖是 MySQL 的一個簡要架構圖,從下圖你可以很清晰的看到用戶的 SQL 語句在 MySQL 內部是如何執行的。 先簡單介紹一下下圖涉及的一些組件的基本作用幫助大家理解這幅圖。 - 連接器: …

JavaWeb筆記01-XML

今日內容 XML 概念語法解析 XML: 概念: Extensible Markup Language 可擴展標記語言 可擴展:標簽都是自定義的.<user><student> 功能 存儲數據 配置文件在網絡中傳輸 一個故事 由于瀏覽器之間的競爭,導致HTML發展的十分不順利 用戶:唉,這怎么報錯了呢?…

centos下如何使用sendmail發送郵件

最近在實施服務端日志監控腳本&#xff0c;需要對異常情況發送郵件通知相關責任人&#xff0c;記錄下centos通過sendmail發送郵件的配置過程。一. 安裝sendmail和mailx1、安裝sendmail&#xff1a;1): centos下可以安裝命令:yum install -y sendmail service sendmail start yu…

H.263 H.263+ Payload Type

h263 rtp協議封裝協議英文版&#xff1a;rfc4629:http://tools.ietf.org/html/rfc4629 以下文章是部分參考翻譯&#xff1a; 文章出處&#xff1a; http://blog.csdn.net/zblue78/archive/2009/04/09/4059414.aspxGeneral H.263 Payload Header The H.263 payload header is s…

OC 中 load 方法和 initialize 方法的異同

(void)load; 當類對象被引入項目時, runtime 會向每一個類對象發送 load 消息load 方法會在每一個類甚至分類被引入時僅調用一次,調用的順序:父類優先于子類, 子類優先于分類load 方法不會被類自動繼承 (void)initialize; 也是在第一次使用這個類的時候會調用這個方法 轉載于:h…

scrapy框架_Python學習之Scrapy框架

爬蟲界江湖地位No.1說起Python&#xff0c;不得不說到它的爬蟲應用&#xff0c;由于Python的短小精悍&#xff0c;用它來開發爬蟲應用是最合適不過了&#xff0c;基于Python抓取網頁的庫有很多&#xff0c;例如requests,beatifulsoup等等&#xff0c;但是要說到有哪一個框架&am…

JavaWeb筆記03-Servlet

今日內容 ServletHTTP協議Request Servlet 概念 步驟 執行原理 生命周期 Servlet3.0注解配置 Servlet的體系結構 Servlet – 接口 GenericServlet – 抽象類:將Servlet接口中其他方法做了默認空實現,只將service()方法作為抽象 將來定義Servlet類時候,可以繼承Generic…

Android開發中無處不在的設計模式——動態代理模式

繼續更新設計模式系列。寫這個模式的主要原因是近期看到了動態代理的代碼。 先來回想一下前5個模式&#xff1a; - Android開發中無處不在的設計模式——單例模式 - Android開發中無處不在的設計模式——Builder模式 - Android開發中無處不在的設計模式——觀察者模式 - A…

用于MPEG-4視聽流的RTP負載格式

MPEG-4的rtp協議封裝英文原版 RFC 3016&#xff1a;http://www.rfc-editor.org/rfc/rfc3016.txt中文翻譯&#xff1a;組織&#xff1a;中國互動出版網&#xff08;http://www.china-pub.com/&#xff09;RFC文檔中文翻譯計劃&#xff08;http://www.china-pub.com/compters/emo…

pycharm python 模板配置_windows下pycharm安裝、創建文件、配置默認模板

本文為大家分享了windows下pycharm安裝、創建文件、配置默認模板的具體步驟&#xff0c;供大家參考&#xff0c;具體內容如下步驟&#xff1a;下包 —->安裝——>創建文件—->定制模板一、下包官方地址這里有企業版和社區版&#xff0c;老司機都知道社區版是免費的&am…

JavaWeb筆記02-Tomcat

今日內容 web相關概念回顧web服務器軟件:TomcatServlet入門學習 web相關概念回顧 軟件架構 C/S: 客戶端/服務器端B/S: 瀏覽器/服務器端 資源分類 靜態資源: 所有用戶訪問后,得到的結果都是一樣的,成為靜態資源,靜態資源可以直接被瀏覽器解析 如:html, css ,JavaScript 動態資…

網上的畫板代碼收集和整理

修改后的代碼[1]為&#xff0c;少了一個} package com.example.administrator.myapplication;import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.graphics.Canvas; import an…

如何寫年終總結(轉)

很多人不重視年終總結&#xff0c;覺得是一個非常令人厭煩的任務&#xff0c;往往是應付了事&#xff0c;短短幾百字&#xff0c;對目前工作中存在的問題發現不夠&#xff0c;思考不足&#xff0c;對自己一年的評價和未來一年的定位沒有說明。造成的后果就是公司得不到來自基層…

cad移動時捕捉不到基點_CAD入門必備(一)移動和復制新手必看

cad也瘋狂前言&#xff1a;CAD繪圖之所以能夠取代手工繪圖&#xff0c;很大的一部分原因是因為它可以很方便的修改和重復利用&#xff0c;例如外參可以節省很大部分時間。而我們在使用CAD中&#xff0c;用得最頻繁的功能就是移動和復制了&#xff0c;當然這也是新手必備的其中一…