Stories

Detail Return Return

Web 網頁性能及性能優化 - Stories Detail

Web 網頁性能及性能優化

一、Web 性能

Web 性能是 Web 開發的一個重要方面,側重於網頁加載速度以及對用户輸入的響應速度

通過優化網站來改善性能,可以在為用户提供更好的體驗

網頁性能既廣泛又非常深入

1. 為什麼性能這麼重要?

1. 性能關乎留住用户

性能對於任何在線業務都至關重要

與加載速度緩慢、讓人感覺運行緩慢的網站相比,加載速度快並能及時響應用户輸入的網站能更好地吸引並留住用户

2. 性能能提高轉化次數

性能會對網站用户是否會瀏覽應用產生重大影響

3. 性能關乎用户體驗

隨着網頁開始加載,用户會等待一段時間,等待內容顯示。在此之前,就談不上用户體驗

快速連接會讓這種體驗一閃而過。而如果連接速度較慢,用户就不得不等待

image

性能是打造良好用户體驗的基本要素

當網站發送大量代碼時,瀏覽器必須使用用户流量套餐中的兆字節流量下載應用

尤其是移動設備的 CPU 性能和內存有限。這可能會導致糟糕的性能條件,而且考慮到人們瞭解人類的行為,用户只能容忍網站上的不利條件長達很長的時間,然後才會放棄網站

2. 網頁核心指標

2.1. 指標類型:
  • 感知加載速度:網頁可以多快地加載網頁中的所有視覺元素並將其渲染到屏幕上
  • 加載響應速度:頁面加載和執行組件快速響應用户互動所需的任何 JavaScript 代碼的速度
  • 運行時響應速度:網頁在加載後對用户互動的響應速度
  • 視覺穩定性:頁面上的元素是否會以用户意想不到的方式發生偏移,是否可能會干擾用户的互動?
  • 流暢性:過渡和動畫是否以一致的幀速率渲染,並在一種狀態之間流暢地流動?
2.2. 要衡量的指標:
  • FCP(First Contentful Paint):從網頁開始加載到網頁內容的任何部分呈現在屏幕上所用的時間
  • LCP(Largest Contentful Paint):從網頁開始加載到屏幕上呈現最大的文本塊或圖片元素所用的時間
  • INP(Interaction to Next Paint):與網頁進行的每次 tapclick 或鍵盤互動的延遲時間,並根據交互的數量選擇頁面中最差的交互延遲作為單個代表性值來描述頁面的總體響應性
  • TBT(Total Blocking Time):從 FCP 到可交互時間 (TTI) 之間的總時長
  • CLS(Cumulative Layout Shift):從頁面開始加載到其生命週期狀態更改為隱藏期間發生的所有意外佈局偏移的累計分數
  • TTFB(Time to First Byte):網絡使用資源的第一個字節響應用户請求所花費的時間
  • FID(First Input Delay):用户首次與網頁互動(即,點擊鏈接、點按按鈕或使用由 JavaScript 提供支持的自定義控件)到瀏覽器實際能夠開始處理事件處理腳本以響應相應互動的時間
2.3. Web 頁面性能衡量指標-以用户為中心的性能指標

Web 頁面性能衡量指標-以用户為中心的性能指標

二、性能優化

1. HTML 頁面性能優化

每個網站都是從請求 HTML 文檔開始的,該請求對網站的加載速度有着重大影響

要想構建可快速加載的網站,第一步就是要及時從服務器接收網頁 HTML 的響應

當在瀏覽器的地址欄中輸入網址時,瀏覽器會向服務器發送 GET 請求進行檢索

網頁的第一個請求針對的是 HTML 資源,因此,確保 HTML 以最短延遲快速到達是關鍵性能目標

1.1. 儘量減少重定向

在請求資源時,服務器可能會做出一個重定向響應,該重定向可以是永久重定向(301 Moved Permanently 響應)或臨時重定向(302 Found 響應)

重定向會降低網頁加載速度,因為它需要瀏覽器在新位置發出額外的 HTTP 請求來檢索資源。重定向有兩種類型:

  1. 完全發生在源站內的同源重定向。這些類型的重定向完全由項目控制,因為管理它們的邏輯完全位於的 Web 服務器上
  2. 由其他源啓動的跨域重定向。這些類型的重定向通常無法控制
1.2. 緩存 HTML 響應

緩存 HTML 響應很困難,因為響應可能包含指向其他關鍵資源(例如 CSSJavaScript、圖片和其他資源類型)的鏈接。這些資源的文件名中可能包含唯一指紋,該指紋會根據文件的內容而變化

但是較短的緩存生命週期(而不是不緩存)具有諸多優勢:

  • 允許在 CDN 中緩存資源,減少從源服務器傳送的請求數量
  • 在瀏覽器中傳送資源,從而重新驗證資源而不是再次下載此
  • 可以將緩存資源的適當時間設置為合適的分鐘數

緩存 HTML 的一種方法是使用 ETagLast-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. 壓縮

基於文本的響應(例如 HTMLJavaScriptCSSSVG 圖片)應進行壓縮,以減小通過網絡傳輸時的大小,從而加快其下載速度。最常用的壓縮算法是 gzipBrotliBrotligzip 提高了約 15% 到 20%。

  • 儘可能使用 Brotli,所有主流瀏覽器都支持 Brotli,但如果網站有大量用户在舊版瀏覽器中使用,請確保將 gzip 用作後備選項,因為任何壓縮都比不進行壓縮要好。
  • 文件大小至關重要。非常小的資源(小於 1 KiB)壓縮得不太好,有時甚至根本壓縮不到。任何類型的數據壓縮的效果都取決於能夠使用壓縮算法找到更多可壓縮數據位的大量數據。文件越大,壓縮效果就越好
  • 瞭解動態壓縮和靜態壓縮。動態壓縮和靜態壓縮是確定何時應壓縮資源的不同方法

    • 動態壓縮會在請求資源時壓縮資源,有時甚至在每次請求資源時壓縮資源。
    • 靜態壓縮消除了壓縮本身涉及的延遲時間,在使用動態壓縮的情況下,這可能會增加服務器響應時間。JavaScriptCSSSVG 圖片等靜態資源應靜態壓縮,而 HTML 資源應動態壓縮。
1.5. CDN

CDN 是分佈式服務器網絡,服務器從源服務器緩存資源,反過來再從物理上更靠近用户的邊緣服務器傳送資源。在距離用户較近時,可以縮短往返時間 (RTT),而 HTTP/2HTTP/3、緩存和壓縮等優化技術則可以讓 CDN 更快地提供內容,而不是從源服務器提取內容。在某些情況下,使用 CDN 可以顯著改善網站的 TTFB

2. 關鍵渲染路徑

關鍵渲染路徑是網頁性能中的一個概念。

關鍵渲染路徑是指網頁開始在瀏覽器中呈現之前所涉及的步驟。為了呈現網頁,瀏覽器需要 HTML 文檔本身以及呈現該文檔所需的所有關鍵資源。

2.1. 漸進式渲染

網絡是自然分佈的。與客户端和 APP 不同,瀏覽器不能依賴於擁有呈現頁面所需的所有資源的網站。因此,瀏覽器非常擅長漸進式呈現頁面。原生應用通常有一個安裝階段,然後是運行階段。然而,對於網頁和網絡應用來説,這兩個階段之間的界限就不那麼明顯了。

2.2. 關鍵渲染路徑

瀏覽器需要知道它應該等待的最小資源數量,以避免呈現明顯不正常的體驗。

另一方面,瀏覽器也不應該等待超過必要的時間才向用户顯示一些內容。瀏覽器在執行初始呈現之前所採取的步驟序列稱為關鍵渲染路徑。

呈現路徑涉及以下步驟:

  • 通過 HTML 構建文檔對象模型 (DOM)
  • 通過 CSS 構建 CSS 對象模型 (CSSOM)
  • 應用任何會更改 DOMCSSOMJavaScript
  • 通過 DOMCSSOM 構建渲染樹
  • 在頁面上執行樣式和佈局操作,看看哪些元素適合顯示
  • 在內存中繪製元素的像素
  • 如果有任何像素重疊,則合成像素
  • 以物理方式將所有生成的像素繪製到屏幕上

在這裏插入圖片描述

只有在完成所有這些步驟後,用户才會在屏幕上看到內容

這一呈現過程會發生多次。初始渲染會調用此流程,但隨着更多會影響網頁渲染的資源可用,瀏覽器將會重新運行此流程(或許只是其中的一部分),以更新用户看到的內容。關鍵渲染路徑側重於之前為初始渲染概述的流程,並依賴於執行初始渲染所需的關鍵資源

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

image

移除未使用的 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

加載不帶 deferasync 屬性的 <script> 元素時,瀏覽器會阻止解析和呈現,直到腳本下載、解析並執行完畢。同樣,內聯腳本也會阻止解析器,直到解析和執行腳本。

3.4.2. async 與 defer

asyncdefer 允許加載外部腳本,而不會阻止 HTML 解析器,而具有 type="module" 的腳本(包括內嵌腳本)會自動延遲。不過,asyncdefer 之間存在一些差異

在這裏插入圖片描述

  • 使用 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 中提供的一系列功能,可以幫助瀏覽器儘早加載資源,甚至可以採用更高的資源優先級來加載資源

資源提示可以告知瀏覽器如何加載資源並確定資源優先級,從而幫助開發者進一步縮短網頁加載時間。初始資源提示(例如 preconnectdns-prefetch)是最先引入的資源提示。隨着時間的推移,preloadFetch 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. 兼容

image

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. 文件格式

瀏覽器支持多種不同的圖片文件格式。與 PNGJPEG 相比,新型圖片格式(例如 WebPAVIF)可提供更好的壓縮效果,從而縮小圖片文件大小,從而縮短下載時間。通過以現代格式提供圖片,可以縮短資源的加載時間,從而降低 Largest Contentful Paint (LCP) 速度。

5.2.1. WebP

WebP 是一種受到廣泛支持的格式,適用於所有新型瀏覽器

WebP 的壓縮效果通常比 JPEGPNGGIF 更好,既能提供有損壓縮,也提供無損壓縮。即使在使用有損壓縮時,WebP 也支持 Alpha 通道透明度,而 JPEG 編解碼器沒有此功能

5.2.2. AVIF

AVIF 是一種較新的圖片格式,雖然沒有 WebP 那麼廣泛支持,但它的跨瀏覽器支持相當得心應

AVIF 同時支持有損壓縮和無損壓縮,並且在某些情況下,與 JPEG 相比,測試的節省幅度超過了 50%。AVIF 還提供廣色域 (WCG) 和高動態範圍 (HDR) 功能

5.3. 壓縮

涉及圖像時,有兩種壓縮類型:

  1. 有損壓縮
  2. 無損壓縮
5.3.1. 有損壓縮

有損壓縮的工作原理是通過量化降低圖片準確性,並且可能會使用色度子採樣捨棄其他顏色信息

有損壓縮在噪聲和顏色多樣的高密度圖像上最有效

有損壓縮可應用於 JPEGWebPAVIF 圖片

使用有損壓縮時,請務必確認壓縮的圖片是否符合的質量標準

5.3.2. 無損壓縮

無損壓縮可以通過在不丟失數據的情況下壓縮圖片來減小文件大小

無損壓縮根據與相鄰像素之間的差異來描述像素

無損壓縮適用於 GIFPNGWebPAVIF 圖片格式

壓縮時,沒有適用於所有情況的通用設置。建議的方法是嘗試使用不同的壓縮級別,直到在圖片質量和文件大小之間找到適當的折衷方案為止

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> 元素時,可以添加對 AVIFWebP 圖片的支持,但如果瀏覽器不支持現代格式,則回退到更兼容的舊圖片格式

<source> 元素還支持 mediasrcsetsizes 屬性。與前面的 <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 Sansfont-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/2HTTP/3,併為網站所需的網頁字體設置正確的緩存標頭

7.4. 僅使用 WOFF2

WOFF2 獲得了廣泛的瀏覽器支持和最佳壓縮效果,比 WOFF 高出 30%。文件縮小可加快下載速度。WOFF2 格式通常是現代瀏覽器實現完全兼容性所需的唯一格式

只有在需要支持舊版瀏覽器時,才可能需要使用其他格式(例如 WOFFEOTTTF)。 如果不需要支持舊版瀏覽器,則沒有理由依賴 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-displayfallback 值在 blockswap 之間折衷。與 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 的哪些部分。

image

代碼拆分是一項可以減少頁面初始 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 緩存後,可在收到請求時幾乎即時提取這些資源

image

如需使用 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 通常被描述為一種單線程語言。這是指主線程,這是瀏覽器執行在瀏覽器中看到的大部分工作的單個線程。其中包括編寫腳本、某些類型的渲染工作、HTMLCSS 解析以及其他類型的面向用户的工作來改善用户體驗等

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 基元和構造,以及 fetchAPI 和相當多的其他 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);
});

三、總結

  • 本文概述了性能以及性能的重要性
  • 羅列了性能優化的點
  • 希望對大家有幫助

引用

  • performance
  • web performance
user avatar yqyx36 Avatar hankin_liu Avatar robin_ren Avatar crow_5c1708a9c847d Avatar onlythinking Avatar gaoxingdeqincai Avatar nanchengfe Avatar miaodaxia Avatar cloudyttt Avatar hole Avatar yinshule Avatar jrainlau Avatar
Favorites 17 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.