Web 性能是 Web 開發的一個重要方面,側重於網頁載入速度以及對用戶輸入的響應速度 通過優化網站來改善性能,可以在為用戶提供更好的體驗 網頁性能既廣泛又非常深入 1. 為什麼性能這麼重要? 1. 性能關乎留住用戶 性能對於任何線上業務都至關重要 與載入速度緩慢、讓人感覺運行緩慢的網站相比,載入速... ...
Web 網頁性能及性能優化
一、Web 性能
Web
性能是 Web
開發的一個重要方面,側重於網頁載入速度以及對用戶輸入的響應速度
通過優化網站來改善性能,可以在為用戶提供更好的體驗
網頁性能既廣泛又非常深入
1. 為什麼性能這麼重要?
1. 性能關乎留住用戶
性能對於任何線上業務都至關重要
與載入速度緩慢、讓人感覺運行緩慢的網站相比,載入速度快並能及時響應用戶輸入的網站能更好地吸引並留住用戶
2. 性能能提高轉化次數
性能會對網站用戶是否會瀏覽應用產生重大影響
3. 性能關乎用戶體驗
隨著網頁開始載入,用戶會等待一段時間,等待內容顯示。在此之前,就談不上用戶體驗
快速連接會讓這種體驗一閃而過。而如果連接速度較慢,用戶就不得不等待
性能是打造良好用戶體驗的基本要素
當網站發送大量代碼時,瀏覽器必須使用用戶流量套餐中的兆位元組流量下載應用
尤其是移動設備的 CPU
性能和記憶體有限。這可能會導致糟糕的性能條件,而且考慮到人們瞭解人類的行為,用戶只能容忍網站上的不利條件長達很長的時間,然後才會放棄網站
2. 網頁核心指標
2.1. 指標類型:
-
感知載入速度:網頁可以多快地載入網頁中的所有視覺元素並將其渲染到屏幕上
-
載入響應速度:頁面載入和執行組件快速響應用戶互動所需的任何
JavaScript
代碼的速度 -
運行時響應速度:網頁在載入後對用戶互動的響應速度
-
視覺穩定性:頁面上的元素是否會以用戶意想不到的方式發生偏移,是否可能會幹擾用戶的互動?
-
流暢性:過渡和動畫是否以一致的幀速率渲染,併在一種狀態之間流暢地流動?
2.2. 要衡量的指標:
-
FCP(First Contentful Paint)
:從網頁開始載入到網頁內容的任何部分呈現在屏幕上所用的時間 -
LCP(Largest Contentful Paint)
:從網頁開始載入到屏幕上呈現最大的文本塊或圖片元素所用的時間 -
INP(Interaction to Next Paint)
:與網頁進行的每次tap
、click
或鍵盤互動的延遲時間,並根據交互的數量選擇頁面中最差的交互延遲作為單個代表性值來描述頁面的總體響應性 -
TBT(Total Blocking Time)
:從FCP
到可交互時間 (TTI
) 之間的總時長 -
CLS(Cumulative Layout Shift)
:從頁面開始載入到其生命周期狀態更改為隱藏期間發生的所有意外佈局偏移的累計分數 -
TTFB(Time to First Byte)
:網路使用資源的第一個位元組響應用戶請求所花費的時間 -
FID(First Input Delay)
:用戶首次與網頁互動(即,點擊鏈接、點按按鈕或使用由JavaScript
提供支持的自定義控制項)到瀏覽器實際能夠開始處理事件處理腳本以響應相應互動的時間
2.3. Web 頁面性能衡量指標-以用戶為中心的性能指標
二、性能優化
1. HTML 頁面性能優化
每個網站都是從請求 HTML
文檔開始的,該請求對網站的載入速度有著重大影響
要想構建可快速載入的網站,第一步就是要及時從伺服器接收網頁 HTML
的響應
當在瀏覽器的地址欄中輸入網址時,瀏覽器會向伺服器發送 GET
請求進行檢索
網頁的第一個請求針對的是 HTML
資源,因此,確保 HTML
以最短延遲快速到達是關鍵性能目標
1.1. 儘量減少重定向
在請求資源時,伺服器可能會做出一個重定向響應,該重定向可以是永久重定向(301 Moved Permanently
響應)或臨時重定向(302 Found
響應)
重定向會降低網頁載入速度,因為它需要瀏覽器在新位置發出額外的 HTTP
請求來檢索資源。重定向有兩種類型:
- 完全發生在源站內的同源重定向。這些類型的重定向完全由項目控制,因為管理它們的邏輯完全位於的
Web
伺服器上 - 由其他源啟動的跨域重定向。這些類型的重定向通常無法控制
1.2. 緩存 HTML 響應
緩存 HTML
響應很困難,因為響應可能包含指向其他關鍵資源(例如 CSS
、JavaScript
、圖片和其他資源類型)的鏈接。這些資源的文件名中可能包含唯一指紋,該指紋會根據文件的內容而變化
但是較短的緩存生命周期(而不是不緩存)具有諸多優勢:
-
允許在
CDN
中緩存資源,減少從源伺服器傳送的請求數量 -
在瀏覽器中傳送資源,從而重新驗證資源而不是再次下載此
-
可以將緩存資源的適當時間設置為合適的分鐘數
緩存 HTML
的一種方法是使用 ETag
或 Last-Modified
響應標頭
ETag
(也稱為實體標記)標頭是一個標識符,用於唯一標識所請求資源,通常使用資源內容的哈希值:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
每當資源發生變化時,都必鬚生成新的 ETag
值。在後續請求中,瀏覽器會通過 If-None-Match
請求標頭髮送 ETag
值。如果伺服器上的 ETag
與瀏覽器發送的 ETag
匹配,伺服器會返回 304 Not Modified
響應,瀏覽器則會使用緩存中的資源。雖然這仍然會導致網路延遲,但 304 Not Modified
響應比整個 HTML
資源小得多
但是,重新驗證資源的新鮮度涉及的網路延遲也本身也是一個缺點,需自行決定以這種方式緩存 HTML
的額外工作是否值得,或者最好是謹慎操作,不必費心緩存 HTML
內容。
1.3. 測量伺服器響應時間
如果響應未緩存,則伺服器的響應時間在很大程度上取決於的托管服務提供商和後端應用堆棧
與動態網頁相比,提供動態生成的響應(例如從資料庫獲取數據)的網頁的 TTFB
可能更高,無需在後端投入大量計算時間即可立即提供
1.4. 壓縮
基於文本的響應(例如 HTML
、JavaScript
、CSS
和 SVG
圖片)應進行壓縮,以減小通過網路傳輸時的大小,從而加快其下載速度。最常用的壓縮演算法是 gzip
和 Brotli
。Brotli
比 gzip
提高了約 15% 到 20%。
- 儘可能使用
Brotli
,所有主流瀏覽器都支持Brotli
,但如果網站有大量用戶在舊版瀏覽器中使用,請確保將gzip
用作後備選項,因為任何壓縮都比不進行壓縮要好。 - 文件大小至關重要。非常小的資源(小於 1
KiB
)壓縮得不太好,有時甚至根本壓縮不到。任何類型的數據壓縮的效果都取決於能夠使用壓縮演算法找到更多可壓縮數據位的大量數據。文件越大,壓縮效果就越好 - 瞭解動態壓縮和靜態壓縮。動態壓縮和靜態壓縮是確定何時應壓縮資源的不同方法
- 動態壓縮會在請求資源時壓縮資源,有時甚至在每次請求資源時壓縮資源。
- 靜態壓縮消除了壓縮本身涉及的延遲時間,在使用動態壓縮的情況下,這可能會增加伺服器響應時間。
JavaScript
、CSS
和SVG
圖片等靜態資源應靜態壓縮,而HTML
資源應動態壓縮。
1.5. CDN
CDN
是分散式伺服器網路,伺服器從源伺服器緩存資源,反過來再從物理上更靠近用戶的邊緣伺服器傳送資源。在距離用戶較近時,可以縮短往返時間 (RTT
),而 HTTP/2
或 HTTP/3
、緩存和壓縮等優化技術則可以讓 CDN
更快地提供內容,而不是從源伺服器提取內容。在某些情況下,使用 CDN
可以顯著改善網站的 TTFB
。
2. 關鍵渲染路徑
關鍵渲染路徑是網頁性能中的一個概念。
關鍵渲染路徑是指網頁開始在瀏覽器中呈現之前所涉及的步驟。為了呈現網頁,瀏覽器需要 HTML
文檔本身以及呈現該文檔所需的所有關鍵資源。
2.1. 漸進式渲染
網路是自然分佈的。與客戶端和 APP 不同,瀏覽器不能依賴於擁有呈現頁面所需的所有資源的網站。因此,瀏覽器非常擅長漸進式呈現頁面。原生應用通常有一個安裝階段,然後是運行階段。然而,對於網頁和網路應用來說,這兩個階段之間的界限就不那麼明顯了。
2.2. 關鍵渲染路徑
瀏覽器需要知道它應該等待的最小資源數量,以避免呈現明顯不正常的體驗。
另一方面,瀏覽器也不應該等待超過必要的時間才向用戶顯示一些內容。瀏覽器在執行初始呈現之前所採取的步驟序列稱為關鍵渲染路徑。
呈現路徑涉及以下步驟:
-
通過
HTML
構建文檔對象模型 (DOM
) -
通過
CSS
構建CSS
對象模型 (CSSOM
) -
應用任何會更改
DOM
或CSSOM
的JavaScript
-
通過
DOM
和CSSOM
構建渲染樹 -
在頁面上執行樣式和佈局操作,看看哪些元素適合顯示
-
在記憶體中繪製元素的像素
-
如果有任何像素重疊,則合成像素
-
以物理方式將所有生成的像素繪製到屏幕上
只有在完成所有這些步驟後,用戶才會在屏幕上看到內容
這一呈現過程會發生多次。初始渲染會調用此流程,但隨著更多會影響網頁渲染的資源可用,瀏覽器將會重新運行此流程(或許只是其中的一部分),以更新用戶看到的內容。關鍵渲染路徑側重於之前為初始渲染概述的流程,並依賴於執行初始渲染所需的關鍵資源
2.3. 關鍵渲染路徑上有哪些資源?
瀏覽器需要等待一些關鍵資源下載完畢,然後才能完成初始渲染。這些資源包括:
HTML
的一部分<head>
元素中阻塞渲染的CSS
<head>
元素中的阻塞渲染的JavaScript
關鍵在於瀏覽器以流式方式處理 HTML
。瀏覽器一旦獲取網頁 HTML
的任何部分,就會開始對其進行處理。然後,瀏覽器就可以(並且通常確實)決定先呈現網頁,然後再接收網頁的其餘部分 HTML
3. 優化資源載入
網頁載入時,其 HTML
中會引用許多資源,通過 CSS
提供網頁的外觀和佈局,並通過 JavaScript
提供互動性。
3.1. 渲染阻塞
CSS
是一種阻塞渲染的資源,因為它會阻止瀏覽器渲染任何內容,直至構建了 CSS
對象模型 (CSSOM
)。瀏覽器會阻止呈現,以防止出現非樣式內容閃爍 (FOUC
)
渲染阻塞未必是不可取的,但需要通過對 CSS
進行優化來最大限度地縮短其持續時間
3.2. 預載入掃描器
3.2.1. 什麼是預載入掃描程式?
預載入掃描程式的角色是推測性,也就是說,它會檢查原始標記,以便查找資源,以便在主要 HTML
解析器發現之前抓取相應資源
預載入掃描程式是一種瀏覽器優化,採用輔助 HTML
解析器的形式,可掃描原始 HTML
響應,以找出並推測性地提取資源,然後主 HTML
解析器才會發現這些資源
為了充分利用預載入掃描器,伺服器發送的 HTML
標記中應包含關鍵資源。預載入掃描器無法發現以下資源載入模式:
- 由
CSS
使用background-image
屬性載入的圖片。這些圖片引用位於CSS
中,預載入掃描器無法發現這些引用 - 動態載入的腳本,採用
<script>
元素標記(使用JavaScript
註入DOM
)或使用動態import()
載入的模塊 - 使用
JavaScript
在客戶端上呈現的HTML
CSS
@import
聲明
這些資源載入模式都是後來發現的資源,請儘可能避免,如果無法避免此類模式,可以使用 preload 提示來避免資源發現延遲
3.3. CSS
CSS
決定了網頁的呈現方式和佈局,CSS
是一種阻止呈現的資源,因此優化 CSS
可能會對整體網頁載入時間產生重大影響
3.3.1. 縮減大小
縮減 CSS
文件大小可縮減 CSS
資源的文件大小,從而縮短下載速度。這主要是通過從 CSS
源文件中移除內容(例如空格和其他不可見字元)並將結果輸出到新優化的文件來實現的:
/* Heading 1 */
h1 {
font-size: 2em;
color: #000000;
}
/* Heading 2 */
h2 {
font-size: 1.5em;
color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}
就最基本的形式而言,CSS
縮減是一種有效的優化,可以提高網站的 FCP
,在某些情況下或許甚至是 LCP
3.3.2. 移除未使用的 CSS
在呈現任何內容之前,瀏覽器需要先下載並解析所有樣式表。完成解析所需的時間還包括當前網頁上未使用的樣式。如果使用的打包器將所有 CSS
資源合併到一個文件中,那麼的用戶下載的 CSS
可能會比呈現當前網頁所需的數量多
如需發現當前網頁未使用的 CSS
,可以使用 Chrome
開發者工具中的 coverage
移除未使用的 CSS
會產生雙重效果:除了縮短下載時間之外,還可以優化渲染樹的構建,因為瀏覽器需要處理的 CSS
規則更少
3.3.3. 避免使用 CSS @import 聲明
CSS
中的 @import
聲明允許從樣式表中導入外部 CSS
資源
可以使用 <link rel="stylesheet">
元素替換 @import
3.3.4. 內嵌關鍵 CSS
關鍵 CSS
是指渲染在初始視窗中可見的內容所需的樣式。初始視窗的概念有時稱為“首屏”。網頁上的其餘內容將保持未設置樣式,而其餘的 CSS
將非同步載入。
但其缺點是,內嵌大量 CSS
會導致初始 HTML
響應的位元組增多。由於 HTML
資源通常無法緩存很長時間(甚至根本無法緩存),因此對於可能在外部樣式表中使用同一 CSS
的後續網頁,系統不會緩存內聯的 CSS
需測試和衡量網頁的性能
3.4. JS
載入過多的 JavaScript
可能會導致網頁在網頁載入期間響應緩慢,甚至可能導致響應速度問題減慢互動速度
3.4.1. 阻止呈現的 JS
載入不帶 defer
或 async
屬性的 <script>
元素時,瀏覽器會阻止解析和呈現,直到腳本下載、解析並執行完畢。同樣,內聯腳本也會阻止解析器,直到解析和執行腳本。
3.4.2. async 與 defer
async
和 defer
允許載入外部腳本,而不會阻止 HTML
解析器,而具有 type="module"
的腳本(包括內嵌腳本)會自動延遲。不過,async
和 defer
之間存在一些差異
使用 async
載入的腳本會在下載後立即解析和執行
使用 defer
載入的腳本會在 HTML 文檔解析完成時執行,這與瀏覽器的 DOMContentLoaded
事件同時發生
async
腳本可能會不按順序執行
defer
腳本則會按照它們在標記中出現的順序執行
使用 type="module"
屬性載入的腳本會處於延遲狀態,而使用 JavaScript
將 <script>
標記註入 DOM
中載入的腳本則像 async
腳本
3.4.3. 客戶端渲染
應避免使用 JavaScript
來呈現任何關鍵內容或網頁的 LCP
元素。這稱為客戶端渲染,是一種在單頁應用 (SPA
) 中廣泛使用的技術
3.4.3.1. LCP 元素
-
<img>
元素(第一幀呈現時間用於GIF
或動畫PNG
等動畫內容) -
<svg>
元素內的<image>
元素 -
<video>
元素(系統會使用視頻的海報圖片載入時間或第一幀顯示時間,以較早者為準) -
一個元素,帶有使用
url()
函數(而不是CSS
漸變)載入的背景圖片 -
包含文本節點或其他內嵌級文本元素子元素的塊級元素
3.4.4. 縮減大小
與 CSS
類似,縮減 JavaScript
大小可縮減腳本資源的文件大小。 這可以加快下載速度,使瀏覽器能夠更快地繼續解析和編譯 JavaScript
的過程
縮減 JavaScript
的大小比縮減其他資源更進一步。縮減 JavaScript
的大小時,不僅會去除空格、製表符和註釋等內容,而且源 JavaScript
中的符號也會被縮短
// Unuglified JavaScript source code:
export function injectScript () {
const scriptElement = document.createElement('script');
scriptElement.src = '/js/scripts.js';
scriptElement.type = 'module';
document.body.appendChild(scriptElement);
}
// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}
4. 通過資源提示協助瀏覽器
資源提示是 HTML
中提供的一系列功能,可以幫助瀏覽器儘早載入資源,甚至可以採用更高的資源優先順序來載入資源
資源提示可以告知瀏覽器如何載入資源並確定資源優先順序,從而幫助開發者進一步縮短網頁載入時間。初始資源提示(例如 preconnect
和 dns-prefetch
)是最先引入的資源提示。隨著時間的推移,preload
和 Fetch Priority API
相繼提供了額外的功能
資源提示會指示瀏覽器提前執行某些操作,這些操作可以提高載入性能。資源提示可以執行操作,例如執行早期 DNS
查找、提前連接到伺服器,甚至在瀏覽器通常發現資源之前提取資源。
資源提示可以在 HTML
中指定(通常在 <head>
元素早期),也可以設置為 HTTP
標頭
4.1. preconnect
preconnect
提示用於與另一個來源(要從其中提取關鍵資源)建立連接
<link rel="preconnect" href="https://example.com">
使用 preconnect
即表示預計瀏覽器計劃在不久的將來連接到特定的跨源伺服器,並且瀏覽器應儘快打開該連接,最好是在等待 HTML
解析器或預載入掃描程式執行此操作之前打開
如果網頁上有大量跨源資源,請對當前網頁最至關重要的資源使用 preconnect
preconnect
的常見用例是 Google Fonts
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
crossorigin
屬性用於指示是否必須使用跨域資源共用 (CORS
) 提取資源。使用 preconnect
提示時,如果從來源下載的資源使用 CORS
(例如字體文件),則需要將 crossorigin
屬性添加到 preconnect
提示中
4.2. dns-prefetch
雖然儘早打開與跨源伺服器的連接可以顯著縮短初始網頁載入時間,但同時與多個跨源伺服器建立連接可能不合理或不可行。如果擔心可能過度使用了 preconnect
,可以使用 dns-prefetch
提示來使用開銷大大降低的資源提示
dns-prefetch
不會與跨源伺服器建立連接,而只是提前為其執行 DNS
查找。在將功能變數名稱解析為其底層 IP
地址時,會發生 DNS
查詢
雖然在設備和網路層級設置 DNS
緩存層有助於使此過程從總體上加快,但仍然需要一些時間
<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
DNS
查找的費用相當低廉,並且由於費用相對較小,在某些情況下,它們可能比 preconnect
更適合
4.3. preload
preload
指令用於提前請求呈現網頁所需的資源
<link rel="preload" href="/lcp-image.jpg" as="image">
preload
指令應僅限於後期發現的關鍵資源
最常見的用例包括字體文件、通過 @import
聲明提取的 CSS
文件,或可能是 LCP
候選對象的 CSS background-image
資源
如果使用 preload 下載由 <img> 元素指定的圖片,該圖片會根據用戶視窗的不同而有所不同
與 preconnect
類似,如果要預載入 CORS
資源(例如字體),則 preload
指令也需要 crossorigin
屬性
如果未添加 crossorigin
屬性(或者為非 CORS
請求添加該屬性),則瀏覽器會下載兩次資源,浪費帶寬,本來可以本該花在其他資源上
<link rel="preload" href="/font.woff2" as="font" crossorigin>
如果 preload
指令的 <link>
元素中缺少 as
屬性,該指令中指定的資源會下載兩次
4.4. prefetch
prefetch
指令用於針對可能會用於未來導航的資源發起低優先順序請求
<link rel="prefetch" href="/next-page.css" as="style">
此指令基本上遵循與 preload
指令相同的格式,只有 <link>
元素的 rel
屬性使用 prefetch
值
與 preload
指令不同,prefetch
主要是推測性的
鑒於 prefetch 的推測性,使用它的這一潛在缺點是,如果用戶沒有轉到最終需要預提取資源的頁面,那麼用於提取資源的數據就可能不會被使用。
4.5. Fetch Priority API
可以通過其 fetchpriority
屬性使用 Fetch Priority API
來提高資源的優先順序。可以將該屬性與 <link>
、<img>
和 <script>
元素一起使用。
<div class="gallery">
<div class="poster">
<img src="img/poster-1.jpg" fetchpriority="high">
</div>
<div class="thumbnails">
<img src="img/thumbnail-2.jpg" fetchpriority="low">
<img src="img/thumbnail-3.jpg" fetchpriority="low">
<img src="img/thumbnail-4.jpg" fetchpriority="low">
</div>
</div>
4.5.1. 值
high
low
auto
4.5.2. 相容
5. 圖片載入性能
圖片代表了當今許多網頁上傳輸的大部分數據
圖片通常是網路上最龐大且最普遍的資源,在大多數情況下,優化圖片意味著通過減少發送的位元組數來減少網路時間,但也可以通過傳送適合用戶設備大小的圖片,從而優化發送給用戶的位元組數
可以使用 <img>
或 <picture>
元素或 CSS background-image
屬性將圖片添加到網頁中
5.1. 圖片大小
使用圖片資源時,可以執行的第一項優化是以正確的尺寸顯示圖片
在不考慮其他變數的情況下,在 500 x 500 像素容器中顯示的圖片的最佳大小為 500 x 500 像素。例如,使用 1000 像素的方形圖片意味著圖片大小將根據需要翻倍
選擇合適的圖片大小涉及許多變數,這使得在任何情況下選擇適當的圖片大小的任務都非常複雜
5.1.1. srcset
<img>
元素支持 srcset
屬性,該屬性可讓指定瀏覽器可能會使用的可能圖片來源的列表
指定的每個圖片來源都必須包含圖片網址,以及寬度或像素密度描述符
<img
alt="An image"
width="500"
height="500"
src="/image-500.jpg"
srcset="/image-500.jpg 1x, /image-1000.jpg 2x, /image-1500.jpg 3x"
>
5.1.2. sizes
藉助 sizes
屬性,可以指定一組來源尺寸,其中每個來源尺寸都由媒體條件和值組成
sizes
屬性用於描述圖片的預期顯示尺寸(以 CSS
像素為單位)
與 srcset
寬度描述符結合使用時,瀏覽器可以選擇哪種圖片來源最適合用戶的設備
<img
alt="An image"
width="500"
height="500"
src="/image-500.jpg"
srcset="/image-500.jpg 500w, /image-1000.jpg 1000w, /image-1500.jpg 1500w"
sizes="(min-width: 768px) 500px, 100vw"
>
如果沒有 sizes
屬性,srcset
寬度描述符將不起作用。同樣,如果省略 srcset
寬度描述符,sizes
屬性也不會執行任何操作
5.2. 文件格式
瀏覽器支持多種不同的圖片文件格式。與 PNG
或 JPEG
相比,新型圖片格式(例如 WebP
和 AVIF
)可提供更好的壓縮效果,從而縮小圖片文件大小,從而縮短下載時間。通過以現代格式提供圖片,可以縮短資源的載入時間,從而降低 Largest Contentful Paint (LCP)
速度。
5.2.1. WebP
WebP
是一種受到廣泛支持的格式,適用於所有新型瀏覽器
WebP
的壓縮效果通常比 JPEG
、PNG
或 GIF
更好,既能提供有損壓縮,也提供無損壓縮。即使在使用有損壓縮時,WebP
也支持 Alpha
通道透明度,而 JPEG
編解碼器沒有此功能
5.2.2. AVIF
AVIF
是一種較新的圖片格式,雖然沒有 WebP
那麼廣泛支持,但它的跨瀏覽器支持相當得心應
AVIF
同時支持有損壓縮和無損壓縮,並且在某些情況下,與 JPEG
相比,測試的節省幅度超過了 50%。AVIF
還提供廣色域 (WCG
) 和高動態範圍 (HDR
) 功能
5.3. 壓縮
涉及圖像時,有兩種壓縮類型:
- 有損壓縮
- 無損壓縮
5.3.1. 有損壓縮
有損壓縮的工作原理是通過量化降低圖片準確性,並且可能會使用色度子採樣捨棄其他顏色信息
有損壓縮在雜訊和顏色多樣的高密度圖像上最有效
有損壓縮可應用於 JPEG
、WebP
和 AVIF
圖片
使用有損壓縮時,請務必確認壓縮的圖片是否符合的質量標準
5.3.2. 無損壓縮
無損壓縮可以通過在不丟失數據的情況下壓縮圖片來減小文件大小
無損壓縮根據與相鄰像素之間的差異來描述像素
無損壓縮適用於 GIF
、PNG
、WebP
和 AVIF
圖片格式
壓縮時,沒有適用於所有情況的通用設置。建議的方法是嘗試使用不同的壓縮級別,直到在圖片質量和文件大小之間找到適當的折衷方案為止
5.4. Picture 元素
<picture>
元素可讓更靈活地指定多個候選圖片
<picture>
<source type="image/avif" srcset="image.avif">
<source type="image/webp" srcset="image.webp">
<img
alt="An image"
width="500"
height="500"
src="/image.jpg"
>
</picture>
當在 <picture>
元素中使用 <source>
元素時,可以添加對 AVIF
和 WebP
圖片的支持,但如果瀏覽器不支持現代格式,則回退到更相容的舊圖片格式
<source>
元素還支持 media
、srcset
和 sizes
屬性。與前面的 <img>
示例類似,這些變數會向瀏覽器指示要在不同視窗上選擇哪個圖片
<picture>
<source
media="(min-resolution: 1.5x)"
srcset="/image-1000.jpg 1000w, /image-1500.jpg 1500w"
sizes="(min-width: 768px) 500px, 100vw"
>
<img
alt="An image"
width="500"
height="500"
src="/image-500.jpg"
>
</picture>
視窗寬度(像素) | 1 DPR | 1.5 DPR | 2 DPR | 3 DPR |
---|---|---|---|---|
320 | 500.jpg | 500.jpg | 500.jpg | 1000.jpg |
480 | 500.jpg | 500.jpg | 1000.jpg | 1500.jpg |
560 | 500.jpg | 1000.jpg | 1000.jpg | 1500.jpg |
1024 | 500.jpg | 1000.jpg | 1000.jpg | 1500.jpg |
1920 | 500.jpg | 1000.jpg | 1000.jpg | 1500.jpg |
<picture>
<source
media="(min-width: 560px) and (min-resolution: 1.5x)"
srcset="/image-1000.jpg 1000w, /image-1500.jpg 1500w"
sizes="(min-width: 768px) 500px, 100vw"
>
<source
media="(max-width: 560px) and (min-resolution: 1.5x)"
srcset="/image-1000-sm.jpg 1000w, /image-1500-sm.jpg 1500w"
sizes="(min-width: 768px) 500px, 100vw"
>
<img
alt="An image"
width="500"
height="500"
src="/image-500.jpg"
>
</picture>
視窗寬度(像素) | 1 DPR | 1.5 DPR | 2 DPR | 3 DPR |
---|---|---|---|---|
320 | 500.jpg | 500.jpg | 500.jpg | 1000-sm.jpg |
480 | 500.jpg | 500.jpg | 1000-sm.jpg | 1500-sm.jpg |
560 | 500.jpg | 1000-sm.jpg | 1000-sm.jpg | 1500-sm.jpg |
1024 | 500.jpg | 1000.jpg | 1000.jpg | 1500.jpg |
1920 | 500.jpg | 1000.jpg | 1000.jpg | 1500.jpg |
5.5. 延遲載入
可以使用 loading
屬性告知瀏覽器在圖片顯示在視窗中時延遲載入圖片
讓瀏覽器可以優先使用渲染視窗中已有的關鍵內容所需的資源
eager
:預設行為,eager
告訴瀏覽器當處理<img>
標簽時立即載入圖片lazy
:告訴用戶代理推遲圖片載入直到瀏覽器認為其需要立即載入時才去載入
5.6. decoding
decoding
屬性會告知瀏覽器應如何解碼圖片
async
:會告知瀏覽器,圖片可以非同步解碼,有可能縮短呈現其他內容的時間sync
:會告知瀏覽器,同步解碼圖像,此圖片應與其他內容同時呈現auto
:允許瀏覽器決定什麼最適合用戶
6. 視頻載入性能
圖片並不是網路上常見的唯一媒體類型。視頻是網頁上常用的另一種媒體類型
6.1. 視頻源文件
處理媒體文件時,在操作系統中識別的文件(.mp4
、.webm
等)稱為容器。一個容器包含一個或多個數據流。在大多數情況下,這是指視頻和音頻流
可以使用編解碼器壓縮每個流。例如,video.webm
可以是 WebM
容器,其中包含使用 VP9
壓縮的視頻流和使用 Vorbis
壓縮的音頻流
壓縮視頻文件的一種方法需要使用 FFmpeg
:
ffmpeg -i input.mov output.webm
6.2. 多種形式
使用視頻文件時,如果瀏覽器不支持所有現代格式,那麼指定多種格式可以作為後備選項
<video>
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
</video>
MP4
可用作舊版瀏覽器的後備方案
6.3. poster 屬性
視頻的海報圖片是使用 <video>
元素上的 poster
屬性添加的,該屬性會在開始播放前向用戶提示視頻內容可能是什麼:
<video poster="poster.jpg">
<source src="video.webm" type="video/webm">
<source src="video.mp4" type="video/mp4">
</video>
6.4. 自動播放
autoplay
在必須立即播放時使用
GIF
動畫可能會非常大,特別是當它有許多包含複雜細節的幀時。動畫 GIF
會消耗數兆位元組的數據並不罕見,這會大量消耗帶寬,以更好地用於更關鍵的資源
通常應該避免使用動畫圖片格式,因為 <video>
等效項對於此類媒體的效率要高得多
具有指定 autoplay
屬性的 <video>
元素會立即開始下載,即使這些元素位於初始視窗之外也是如此
通過結合使用 poster
屬性與 Intersection Observer API
,可以將頁面配置為僅在視頻位於視窗內時下載
6.5. preload
可以使用 <video>
元素的 preload
屬性來影響為視頻資源下載的內容:
- 設置
preload="none"
可告知瀏覽器不應預載入任何視頻內容 - 設置
preload="metadata"
僅提取視頻元數據,例如視頻時長,可能還有一些其他粗略信息
如果要載入用戶需要開始播放的視頻,則最好設置 preload="none"
7. 優化網頁字體
網路字體是網路上的常用資源
網路字體會影響網頁在載入時和呈現時的性能
較大的字體文件可能需要一段時間才能下載完畢,並且會對 First Contentful Paint (FCP)
產生負面影響,而不正確的 font-display
值則可能會導致不必要的佈局偏移,進而導致網頁的 Cumulative Layout Shift (CLS)
7.1. @font-face
@font-face {
font-family: "Open Sans";
src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2");
}
上述代碼段定義了一個名為 Open Sans
的 font-family
,並告知瀏覽器在哪裡可以找到相應的網頁字體資源。為了節省帶寬,瀏覽器在確定當前頁面的佈局需要網頁字體之前,不會下載該字體
7.2. preload
如果的 @font-face
聲明是在外部樣式表中定義的,瀏覽器只有在下載該樣式表之後才能開始下載這些聲明。這使得網路字體資源被延遲發現,但有一些方法可以幫助瀏覽器更快地發現網路字體
可以使用 preload
指令發起對網頁字體資源的提前請求。preload
指令可讓網頁字體在網頁載入初期被檢測到,瀏覽器會立即開始下載這些字體,無需等待樣式表完成下載和解析
preload
指令不會等到網頁上需要相應字體時再執行
<link rel="preload" as="font" href="/fonts/OpenSans-Regular-webfont.woff2" crossorigin>
請謹慎使用 preload
指令。過度使用 preload
指令可能會中斷其他關鍵資源的帶寬
字體屬於 CORS
資源,預載入字體時必須指定 crossorigin
屬性,即使這些字體是自托管的字體也是如此
7.3. 自行托管網頁字體
可以通過自行托管網頁字體來消除對第三方連接的需要。在大多數情況下,自托管網路字體比從跨源下載字體更快。如果打算自行托管網頁字體,請檢查的網站是否使用了內容分髮網絡 (CDN
)、HTTP/2
或 HTTP/3
,併為網站所需的網頁字體設置正確的緩存標頭
7.4. 僅使用 WOFF2
WOFF2
獲得了廣泛的瀏覽器支持和最佳壓縮效果,比 WOFF
高出 30%。文件縮小可加快下載速度。WOFF2
格式通常是現代瀏覽器實現完全相容性所需的唯一格式
只有在需要支持舊版瀏覽器時,才可能需要使用其他格式(例如 WOFF
、EOT
和 TTF
)。 如果不需要支持舊版瀏覽器,則沒有理由依賴 WOFF2
以外的網頁字體格式
7.5. 設置網頁字體子集
網路字體通常包含各種不同的字形,需要這些字形來表示不同語言中使用的各種字元。如果的網頁僅以一種語言(或使用單一字母表)提供內容,可以通過子集內嵌來減小網頁字體的大小。此操作通常通過指定數字或 Unicode
碼位範圍來實現
子集是原始網頁字體文件中包含的減少的字形集。例如,的網頁可能會提供部分拉丁字元,而不是提供所有字形。根據所需的子集,移除字形可以顯著減小字體文件的大小
7.6. 字體渲染
瀏覽器發現並下載某種網頁字體後,就可以進行渲染了。預設情況下,在下載使用網頁字體的任何文本之前,瀏覽器都會阻止其渲染。可以使用 font-display CSS
屬性調整瀏覽器的文本渲染行為,並配置在網頁字體完全載入之前應顯示(或不顯示)哪些文本
7.6.1. block
font-display
的預設值為 block
。使用 block
時,瀏覽器會阻止呈現使用指定網頁字體的任何文本。不同瀏覽器的行為會略有不同
7.6.2. swap
swap
是使用最廣泛的 font-display
值。swap
不會阻止渲染,並且會在交換成指定的網頁字體之前立即以後備方式顯示文本。這樣,就可以立即顯示內容,而無需等待網路字體下載完成
7.6.3. fallback
font-display
的 fallback
值在 block
和 swap
之間折衷。與 swap
不同,瀏覽器會阻止字體渲染,但只能在很短的時間內交換回退文本。不過,與 block
不同的是,阻塞期極短
7.6.4. optional
optional
是最嚴格的 font-display
值,僅在 100 毫秒內下載時才會使用網頁字體資源
如果某種網頁字體的載入用時超過該時長,便不會在網頁上使用,因此瀏覽器會使用後備字體進行當前導航,同時在後臺下載該網頁字體並將其存放在瀏覽器緩存中
7.6.5. auto
8. 代碼拆分 JavaScript
有些資源對網頁的初始載入並不重要。JavaScript
就是這樣一種資源,可通過稱為代碼拆分的技術推遲到需要時
這樣一來,可以通過減少帶寬和 CPU
爭用來提高性能,這是提高初始網頁載入速度和啟動期間的輸入響應速度的關鍵因素
載入大型 JavaScript
資源會顯著影響網頁速度。將 JavaScript
拆分為較小的區塊並僅下載網頁在啟動期間正常運行所必需的內容,可以極大地提高網頁的載入響應能力,進而提高網頁的互動到下一次繪製 (INP
)
8.1. 通過代碼拆分,減少啟動期間的 JavaScript 解析和執行
Lighthouse
會在 JavaScript
執行時間超過 2 秒時發出警告,併在執行時間超過 3.5 秒時失敗
在網頁生命周期的任何時間點,過度的 JavaScript
解析和執行都是潛在的問題,因為如果用戶與網頁互動的時間與負責處理和執行 JavaScript
的主線程任務運行的時間一致,則有可能會增加互動的輸入延遲時間
可以使用 Chrome
開發者工具中的覆蓋率工具(coverage
)進一步確定頁面載入期間未使用頁面的 JavaScript
的哪些部分。
代碼拆分是一項可以減少頁面初始 JavaScript
載荷的實用技術。它可讓將 JavaScript
軟體包拆分為兩部分:
- 網頁載入時所需的
JavaScript
無法在任何其他時間載入 - 可在稍後時間點載入(最常見的是用戶與頁面上的指定互動元素互動時)的其餘
JavaScript
8.2. import()
可以使用動態 import()
語法完成代碼拆分。此語法與在啟動期間請求指定 JavaScript
資源的 <script>
元素不同,該語法可在網頁生命周期的後期請求 JavaScript
資源
動態 import()
是一種類似於函數的表達式,可讓動態載入 JavaScript
模塊。 它是一種非同步操作,可用於導入模塊以響應互動或需要載入其他模塊的其他任何條件。動態 import()
與靜態import
語句不同,後者會立即導入模塊,並且要求父模塊及其所有依賴項都得到解析和執行,然後才能運行
document.querySelectorAll('#myForm input').addEventListener('blur', async () => {
const { validateForm } = await import('/validate-form.mjs');
validateForm();
}, { once: true });
9. 延遲載入 <iframe>元素
<iframe>
元素可能會占用大量帶寬和 CPU
處理時間
與其他類型的資源相比,<iframe>
元素消耗的帶寬通常更多。對於 <iframe>
元素,載入和渲染其中的頁面可能會消耗相當多的額外處理時間
9.1. <iframe> 元素的 loading 屬性
所有主流瀏覽器也都支持 <iframe>
元素上的 loading
屬性
loading
屬性的值及其行為與使用 loading
屬性的 <img>
元素相同:
eager
為預設值lazy
會延遲載入<iframe>
元素的HTML
及其子資源,直到該元素與視窗之間的距離在預定義的距離以內
9.2. JavaScript 延遲載入
使用 Intersection Observer API
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lazy Load Images</title>
<style>
.spacer {
height: 100vh;
}
.lazy {
width: 100%;
height: auto;
display: block;
}
</style>
</head>
<body>
<div class="spacer"></div>
<img class="lazy" data-src="https://via.placeholder.com/600x400" alt="Lazy Image 1">
<div class="spacer"></div>
<img class="lazy" data-src="https://via.placeholder.com/600x400" alt="Lazy Image 2">
<div class="spacer"></div>
<img class="lazy" data-src="https://via.placeholder.com/600x400" alt="Lazy Image 3">
<div class="spacer"></div>
</body>
</html>
<script>
// 回調函數,當目標元素的可見性發生變化時調用
const lazyLoad = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.getAttribute('data-src');
img.onload = () => img.removeAttribute('data-src');
observer.unobserve(img);
}
});
};
// IntersectionObserver 配置
const options = {
root: null, // 預設是視窗
rootMargin: '0px',
threshold: 0.1 // 目標元素進入視窗 10% 時觸發回調
};
// 創建 IntersectionObserver 實例
const observer = new IntersectionObserver(lazyLoad, options);
// 觀察所有具有 'lazy' 類的圖片
document.querySelectorAll('img.lazy').forEach(img => observer.observe(img));
</script>
-
root
:用作視窗的元素,用於檢查目標的可見性,如果未指定或為 null,則預設為瀏覽器視窗。 -
rootMargin
:根周圍的邊距 -
threshold
:一個數字或一個數字數組,表示目標可見度達到多少百分比時,觀察器的回調就應該執行。如果只想在能見度超過 50% 時檢測,可以使用 0.5 的值。如果希望每次能見度超過 25% 時都執行回調,則需要指定數組 [0, 0.25, 0.5, 0.75, 1]。預設值為 0(這意味著只要有一個像素可見,回調就會運行)。值為 1.0 意味著在每個像素都可見之前,閾值不會被認為已通過。
10. 預提取、預渲染和 Service Worker 預緩存
雖然許多性能涉及到可以採取哪些措施來優化和消除不必要的資源,但建議先載入一些資源才是需要用到的,這似乎有點自相矛盾。不過,在某些情況下,可以提前載入某些資源
10.1. prefetch
可以使用 <link rel="prefetch">
資源提示提前提取資源(包括圖片、樣式表或 JavaScript
資源)。prefetch
提示用於告知瀏覽器在不久的將來可能需要某個資源
指定 prefetch
提示後,瀏覽器可能會以最低優先順序發起對該資源的請求,以避免與當前頁面所需的資源發生爭用
預提取資源可以改善用戶體驗,因為用戶無需等待近期所需的資源下載完畢,因為可以在需要時立即從磁碟緩存中檢索這些資源
<head>
<link rel="prefetch" as="script" href="/date-picker.js">
<link rel="prefetch" as="style" href="/date-picker.css">
</head>
還可以通過在指向某個 HTML
文檔時指定 as="document"
屬性來預提取網頁及其所有子資源
<link rel="prefetch" href="/page" as="document">
在基於 Chromium
的瀏覽器中,可以使用 Speculation Rules API
預提取文檔。推測規則定義為包含在網頁的 HTML
中的 JSON
對象,或通過 JavaScript
動態添加:
<script type="speculationrules">
{
"prefetch": [{
"source": "list",
"urls": ["/page-a", "/page-b"]
}]
}
</script>
10.2. prerender
除了預提取資源之外,還可以提示瀏覽器在用戶導航到某個網頁之前預呈現該網頁
這種做法幾乎可以即時載入網頁,因為系統會在後臺提取和處理網頁及其資源。當用戶導航到相應頁面後,系統會將該頁面置於前臺
Speculation Rules API
支持預渲染:
<script type="speculationrules">
{
"prerender": [
{
"source": "list",
"urls": ["/page-a", "page-b"]
}
]
}
</script>
10.3. Service Worker 預緩存
還可以使用 Service Worker
推測性地預提取資源
Service Worker
預緩存可以使用 CacheAPI
提取和保存資源,這樣瀏覽器無需訪問網路即可使用 Cache API
處理請求
Service Worker
預緩存使用一種非常有效的 Service Worker
緩存策略,稱為“僅緩存”策略。這種模式非常有效,因為將資源放入 Service Worker
緩存後,可在收到請求時幾乎即時提取這些資源
如需使用 Service Worker
預緩存資源,可以使用 Workbox
Workbox
使用預緩存清單來確定應預緩存的資源,預緩存清單是一個文件和版本控制信息列表,可作為要預緩存的資源的可信來源
[{
url: 'script.ffaa4455.js',
revision: null
}, {
url: '/index.html',
revision: '518747aa'
}]
上述代碼是一個示例清單,其中包含 script.ffaa4455.js
和 /index.html
這兩個文件。如果資源在文件本身中包含版本信息(稱為文件哈希),則 revision
屬性可以保留為 null
,因為文件已進行版本控制(例如,上述代碼中 script.ffaa4455.js
資源的 ffaa4455
屬性)。
設置後,Service Worker
可用於預緩存靜態頁面或其子資源,以加快後續頁面導航的速度
workbox.precaching.precacheAndRoute([
'/styles/product-page.ac29.css',
'/styles/product-page.39a1.js',
]);
Service Worker
使用的 Cache
介面和 HTTP
緩存並不相同
Cache
介面是由 JavaScript
控制的高層級緩存,而 HTTP
緩存是由 Cache-Control
標頭控制的低層級緩存
與使用資源提示或推測規則預提取或預呈現資源類似,Service Worker
預緩存會消耗網路帶寬、存儲空間和 CPU
建議僅預緩存可能會使用的資源,併在預緩存清單中指定過多的資源
11. Web Worker
用戶在瀏覽器中看到的大部分內容都在稱為主線程的單個線程上完成。不過,在某些情況下,可以啟動新線程來執行計算開銷很大的工作,以便主線程可以處理面向用戶的重要任務。執行此操作的 API
稱為 Web Worker API
JavaScript
通常被描述為一種單線程語言。這是指主線程,這是瀏覽器執行在瀏覽器中看到的大部分工作的單個線程。其中包括編寫腳本、某些類型的渲染工作、HTML
和 CSS
解析以及其他類型的面向用戶的工作來改善用戶體驗等
就 JavaScript
而言,通常只能在主線程上執行工作,但可以在 JavaScript
中註冊和使用其他線程。允許在 JavaScript
中實現多線程的功能稱為 Web Workers API
11.1. Web Worker 啟動方式
實例化 Worker
類
const myWebWorker = new Worker('/my-web-worker.js');
11.2. Web Worker 的限制
與在主線程上運行的 JavaScript
不同,Web Worker
無法直接訪問 window上下文
,並且對其提供的 API
的訪問受到限制。Web Worker
受到以下限制條件的約束:
Web Worker
無法直接訪問DOM
Web Worker
可以通過消息傳遞流水線與window
上下文進行通信,這意味著Web Worker
可以通過某種方式間接訪問DOM
Web Worker
的作用域是self
,而不是window
Web Worker
範圍_確實_可以訪問JavaScript
基元和構造,以及fetch
等API
和相當多的其他API
11.3. Web Worker 如何與 window 通信
Web Worker
可以通過消息傳遞流水線與主線程的 window
上下文進行通信。利用此流水線,可以將數據傳送到主線程和 Web
工作器以及從主線程和 Web
工作器傳輸數據。如需將數據從 Web Worker
發送到主線程,需要在 Web Worker
的上下文 (self
) 中設置 message
事件
// my-web-worker.js
self.addEventListener("message", () => {
// Sends a message of "Hellow, window!" from the web worker:
self.postMessage("Hello, window!");
});
然後,在主線程上 window
上下文的腳本中,可以使用另一個 message
事件接收來自網頁工作器線程的消息:
// scripts.js
// Creates the web worker:
const myWebWorker = new Worker('/js/my-web-worker.js');
// Adds an event listener on the web worker instance that listens for messages:
myWebWorker.addEventListener("message", ({ data }) => {
// Echoes "Hello, window!" to the console from the worker.
console.log(data);
});
三、總結
- 本文概述了性能以及性能的重要性
- 羅列了性能優化的點
- 希望對大家有幫助