2021 年前端性能:交付優化

已發表: 2022-03-10
快速總結↬讓2021年……快點! 一份年度前端性能清單,包含您在當今 Web 上創建快速體驗所需了解的所有內容,從指標到工具和前端技術。 自 2016 年以來更新。

目錄

  1. 準備:計劃和指標
  2. 設定切合實際的目標
  3. 定義環境
  4. 資產優化
  5. 構建優化
  6. 交付優化
  7. 網絡、HTTP/2、HTTP/3
  8. 測試和監控
  9. 快速獲勝
  10. 一切都在一頁上
  11. 下載清單(PDF、Apple Pages、MS Word)
  12. 訂閱我們的電子郵件通訊不要錯過下一個指南。

交付優化

  1. 我們是否使用defer來異步加載關鍵的 JavaScript?
    當用戶請求一個頁面時,瀏覽器獲取 HTML 並構造 DOM,然後獲取 CSS 並構造 CSSOM,然後通過匹配 DOM 和 CSSOM 生成渲染樹。 如果需要解析任何 JavaScript,瀏覽器在解析之前不會開始渲染頁面,從而延遲渲染。 作為開發人員,我們必須明確告訴瀏覽器不要等待並開始渲染頁面。 對腳本執行此操作的方法是使用 HTML 中的deferasync屬性。

    在實踐中,事實證明最好使用defer而不是async 。 啊,又有什麼區別呢? 根據 Steve Souders 的說法,一旦async腳本到達,它們就會立即執行——只要腳本準備好。 如果這種情況發生得非常快,例如當腳本處於緩存中時,它實際上會阻塞 HTML 解析器。 使用defer ,瀏覽器在解析 HTML 之前不會執行腳本。 因此,除非您需要在開始渲染之前執行 JavaScript,否則最好使用defer 。 此外,多個異步文件將以不確定的順序執行。

    值得注意的是,關於asyncdefer存在一些誤解。 最重要的是, async並不意味著只要腳本準備好,代碼就會運行。 這意味著只要腳本準備好並且所有先前的同步工作完成,它將運行。 用 Harry Roberts 的話來說,“如果你在同步腳本之後放置一個async腳本,那麼你的async腳本只會和最慢的同步腳本一樣快。”

    此外,不建議同時使用asyncdefer 。 現代瀏覽器同時支持這兩種屬性,但只要同時使用這兩種屬性, async總是會勝出的。

    如果您想深入了解更多細節,Milica Mihajlija 編寫了一份關於更快構建 DOM 的非常詳細的指南,詳細介紹了推測解析、異步和延遲。

  2. 使用 IntersectionObserver 和優先級提示延遲加載昂貴的組件。
    一般來說,建議延遲加載所有昂貴的組件,例如繁重的 JavaScript、視頻、iframe、小部件和潛在的圖像。 本機延遲加載已經可用於具有loading屬性的圖像和 iframe(僅限 Chromium)。 在幕後,此屬性延遲資源的加載,直到它到達與視口的計算距離。
    <!-- Lazy loading for images, iframes, scripts. Probably for images outside of the viewport. --> <img loading="lazy" ... /> <iframe loading="lazy" ... /> <!-- Prompt an early download of an asset. For critical images, eg hero images. --> <img loading="eager" ... /> <iframe loading="eager" ... />

    該閾值取決於幾件事,從要獲取的圖像資源的類型到有效的連接類型。 但在 Android 上使用 Chrome 進行的實驗表明,在 4G 上,97.5% 的延遲加載的首屏圖像在可見後 10 毫秒內完全加載,因此應該是安全的。

    我們還可以在<script><img><link>元素(僅閃爍)上使用importance屬性( highlow )。 事實上,這是一種在輪播中取消圖像優先級以及重新確定腳本優先級的好方法。 但是,有時我們可能需要更精細的控制。

    <!-- When the browser assigns "High" priority to an image, but we don't actually want that. --> <img src="less-important-image.svg" importance="low" ... /> <!-- We want to initiate an early fetch for a resource, but also deprioritize it. --> <link rel="preload" importance="low" href="/script.js" as="script" />

    執行稍微複雜一點的延遲加載的最高效方法是使用 Intersection Observer API,它提供了一種異步觀察目標元素與祖先元素或頂級文檔視口的交集變化的方法。 基本上,您需要創建一個新的IntersectionObserver對象,該對象接收一個回調函數和一組選項。 然後我們添加一個目標來觀察。

    回調函數在目標變得可見或不可見時執行,因此當它攔截視口時,您可以在元素變得可見之前開始執行一些操作。 事實上,我們可以通過rootMargin (根周圍的邊距)和threshold (單個數字或數字數組,指示我們瞄準的目標可見性的百分比)對何時調用觀察者的回調進行精細控制。

    Alejandro Garcia Anglada 發布了一個關於如何實際實現它的方便教程,Rahul Nanwani 寫了一篇關於延遲加載前景和背景圖像的詳細文章,Google Fundamentals 也提供了關於延遲加載圖像和視頻的詳細教程,以及 Intersection Observer。

    還記得帶有移動和粘性物體的藝術指導的長篇故事嗎? 您也可以使用 Intersection Observer 實現高性能的滾動顯示。

    再次檢查您可以延遲加載的其他內容。 甚至延遲加載翻譯字符串和表情符號也會有所幫助。 通過這樣做,Mobile Twitter 成功地將新的國際化管道的 JavaScript 執行速度提高了 80%。

    一個簡短的警告:值得注意的是延遲加載應該是一個例外而不是規則。 延遲加載您真正希望人們快速看到的任何內容可能是不合理的,例如產品頁面圖像、英雄圖像或主導航變為交互所需的腳本。

一個示例顯示了 3000 像素的舊閾值和 160 KB 的下載(左),而新閾值的數量為 1250 像素,只有 90 KB 的下載(右)顯示了 img 加載延遲數據節省的改進
在快速連接(例如 4G)上,Chrome 的視口距離閾值最近從 3000 像素降低到 1250 像素,而在較慢的連接(例如 3G)上,閾值從 4000 像素更改為 2500 像素。 (大預覽)
帶有顯示 Twitter UI 的手機周圍的文字插圖,解釋了延遲加載翻譯字符串的工具改進
通過延遲加載翻譯字符串,Mobile Twitter 成功地將新國際化管道的 JavaScript 執行速度提高了 80%。 (圖片來源:Addy Osmani)(大預覽)
  1. 逐步加載圖像。
    您甚至可以通過向頁面添加漸進式圖像加載來將延遲加載提升到一個新的水平。 與 Facebook、Pinterest、Medium 和 Wolt 類似,您可以先加載低質量甚至模糊的圖像,然後隨著頁面繼續加載,使用 BlurHash 技術或 LQIP(低質量圖像佔位符)將它們替換為完整質量版本技術。

    如果這些技術是否改善了用戶體驗,意見會有所不同,但它肯定會縮短首次內容繪製的時間。 我們甚至可以通過使用 SQIP 來自動化它,該 SQIP 創建一個低質量版本的圖像作為 SVG 佔位符,或帶有 CSS 線性漸變的漸變圖像佔位符。

    這些佔位符可以嵌入到 HTML 中,因為它們自然可以很好地使用文本壓縮方法進行壓縮。 在他的文章中,Dean Hume 描述瞭如何使用 Intersection Observer 來實現這種技術。

    倒退? 如果瀏覽器不支持交叉點觀察器,我們仍然可以延遲加載 polyfill 或立即加載圖像。 甚至還有一個圖書館。

    想要更高級? 您可以跟踪圖像並使用原始形狀和邊緣來創建輕量級 SVG 佔位符,首先加載它,然後從占位符矢量圖像過渡到(加載的)位圖圖像。

  2. 三個不同的版本展示了 Jose M. Perez 的 SVG 延遲加載技術,左側是類似於立體主義藝術的版本,中間是像素化模糊版本,右側是 Jose 本人的正確照片
    Jose M. Perez 的 SVG 延遲加載技術。 (大預覽)
  3. 您是否延遲渲染content-visibility
    對於包含大量內容塊、圖像和視頻的複雜佈局,解碼數據和渲染像素可能是一項相當昂貴的操作——尤其是在低端設備上。 使用content-visibility: auto ,我們可以在容器位於視口之外時提示瀏覽器跳過子項的佈局。

    例如,您可能會在初始加載時跳過頁腳和後期部分的呈現:

    footer { content-visibility: auto; contain-intrinsic-size: 1000px; /* 1000px is an estimated height for sections that are not rendered yet. */ }

    注意content-visibility: auto; 表現得像溢出:隱藏; ,但您可以通過應用padding-leftpadding-right而不是默認的margin-left: auto;margin-right: auto; 和聲明的寬度。 填充基本上允許元素溢出內容框並進入填充框,而不會離開整個盒子模型並被切斷。

    另外,請記住,當最終呈現新內容時,您可能會引入一些 CLS,因此最好將contain-intrinsic-size與適當大小的佔位符一起使用(謝謝,Una! )。

    Thijs Terluin 有更多關於這兩個屬性以及瀏覽器如何計算 contains contain-intrinsic-size的詳細信息,Malte Ubl 展示瞭如何計算它,Jake 和 Surma 的簡短視頻解釋器解釋了它是如何工作的。

    如果您需要更細化,使用 CSS 包含,如果您只需要其他元素的大小、對齊或計算樣式,您可以手動跳過 DOM 節點後代的佈局、樣式和繪製工作——或者該元素當前是畫布外。

初始加載時的渲染性能對於基線(左)為 2,288 毫秒,對於具有 content-visibility:auto 的塊為 13,464 毫秒(右)
在演示中,將content-visibility: auto應用於分塊內容區域可在初始加載時將渲染性能提升 7 倍。 (大預覽)
  1. 您是否使用decoding="async"延遲解碼?
    有時內容會出現在屏幕外,但我們希望確保它在客戶需要時可用——理想情況下,不阻塞關鍵路徑中的任何內容,而是異步解碼和呈現。 我們可以使用decode decoding="async"來授予瀏覽器從主線程解碼圖像的權限,避免用戶對用於解碼圖像的CPU時間的影響(通過Malte Ubl):

    <img decoding="async" … />

    或者,對於屏幕外的圖像,我們可以先顯示一個佔位符,當圖像在視口內時,使用 IntersectionObserver 觸發網絡調用以在後台下載圖像。 此外,如果 Image Decode API 不可用,我們可以將渲染推遲到使用 img.decode() 解碼或下載圖像。

    例如,在渲染圖像時,我們可以使用淡入動畫。 Katie Hempenius 和 Addy Osmani 在他們的演講 Speed at Scale: Web Performance Tips and Tricks from the Trenches 中分享了更多見解。

  2. 您是否生成並提供關鍵的 CSS?
    為了確保瀏覽器盡快開始呈現您的頁面,收集開始呈現頁面第一個可見部分所需的所有 CSS(稱為“關鍵 CSS”或“首屏 CSS”已成為一種常見做法") 並將其包含在頁面的<head>中,從而減少往返。 由於在慢啟動階段交換的包的大小有限,關鍵 CSS 的預算約為 14KB。

    如果超出此範圍,瀏覽器將需要額外的往返來獲取更多樣式。 CriticalCSS 和 Critical 使您能夠為您正在使用的每個模板輸出關鍵 CSS。 不過,根據我們的經驗,沒有任何自動系統比為每個模板手動收集關鍵 CSS 更好,而這確實是我們最近重新採用的方法。

    然後,您可以使用 critters Webpack 插件內聯關鍵 CSS 並延遲加載其餘部分。 如果可能,請考慮使用 Filament Group 使用的條件內聯方法,或動態將內聯代碼轉換為靜態資源。

    如果您當前使用諸如 loadCSS 之類的庫異步加載完整的 CSS ,則實際上沒有必要。 使用media="print" ,您可以欺騙瀏覽器異步獲取 CSS,但在加載後應用到屏幕環境。 (謝謝​​,斯科特!

    <!-- Via Scott Jehl. https://www.filamentgroup.com/lab/load-css-simpler/ --> <!-- Load CSS asynchronously, with low priority --> <link rel="stylesheet" href="full.css" media="print" onload="this.media='all'" />

    在收集每個模板的所有關鍵 CSS 時,通常只探索“首屏”區域。 但是,對於復雜的佈局,最好將佈局的基礎也包括在內,以避免大量的重新計算和重新繪製成本,從而損害您的 Core Web Vitals 分數。

    如果用戶得到一個直接鏈接到頁面中間的 URL 但 CSS 尚未下載怎麼辦? 在這種情況下,隱藏非關鍵內容變得很常見,例如opacity: 0; 內聯 CSS 和opacity: 1在完整的 CSS 文件中,並在 CSS 可用時顯示。 但它有一個主要缺點,因為連接速度較慢的用戶可能永遠無法閱讀頁面的內容。 這就是為什麼最好始終保持內容可見,即使它的樣式可能不正確。

    由於緩存,將關鍵 CSS(和其他重要資產)放在根域上的單獨文件中具有好處,有時甚至超過內聯。 Chrome 在請求頁面時會推測性地打開到根域的第二個 HTTP 連接,這樣就不需要 TCP 連接來獲取此 CSS。 這意味著您可以創建一組關鍵的-CSS 文件(例如, critical-homepage.csscritical-product-page.css等)並從您的根目錄提供它們,而無需內聯它們。 (謝謝​​,菲利普!

    需要注意的是:使用 HTTP/2,關鍵 CSS 可以存儲在單獨的 CSS 文件中,並通過服務器推送交付,而不會使 HTML 膨脹。 問題是服務器推送對於跨瀏覽器的許多陷阱和競爭條件很麻煩。 它從來沒有得到一致的支持,並且存在一些緩存問題(參見 Hooman Beheshti 演示文稿的幻燈片 114)。

    事實上,這種影響可能是負面的,並且會使網絡緩衝區膨脹,從而阻止傳遞文檔中的真實幀。 因此,Chrome 計劃暫時取消對 Server Push 的支持也就不足為奇了。

  3. 嘗試重新組合 CSS 規則。
    我們已經習慣了關鍵的 CSS,但還有一些優化可以超越這一點。 哈里·羅伯茨(Harry Roberts)進行了一項非凡的研究,結果令人驚訝。 例如,將主 CSS 文件拆分為單獨的媒體查詢可能是個好主意。 這樣,瀏覽器將檢索具有高優先級的關鍵 CSS,並以低優先級檢索其他所有內容——完全脫離關鍵路徑。

    另外,避免將<link rel="stylesheet" />放在async片段之前。 如果腳本不依賴於樣式表,請考慮將阻塞腳本置於阻塞樣式之上。 如果是這樣,則將該 JavaScript 分成兩部分並將其加載到 CSS 的任一側。

    Scott Jehl 通過使用 service worker 緩存內聯 CSS 文件解決了另一個有趣的問題,如果您使用關鍵 CSS,這是一個常見的問題。 基本上,我們在style元素上添加一個 ID 屬性,以便使用 JavaScript 輕鬆找到它,然後一小段 JavaScript 找到該 CSS 並使用 Cache API 將其存儲在本地瀏覽器緩存中(內容類型為text/css ) 用於後續頁面。 為了避免在後續頁面上內聯,而是在外部引用緩存的資產,我們在第一次訪問網站時設置了一個 cookie。 瞧!

    值得注意的是,動態​​樣式也可能很昂貴,但通常僅在您依賴數百個同時呈現的組合組件的情況下。 因此,如果您使用 CSS-in-JS,請確保您的 CSS-in-JS 庫在您的 CSS 不依賴主題或道具時優化執行,並且不要過度組合樣式化的組件。 Aggelos Arvanitakis 分享了關於 CSS-in-JS 的性能成本的更多見解。

  4. 您是否流式傳輸響應?
    流經常被遺忘和忽視,流提供了一個用於讀取或寫入異步數據塊的接口,在任何給定時間,內存中可能只有其中的一個子集可用。 基本上,它們允許發出原始請求的頁面在第一塊數據可用時立即開始處理響應,並使用針對流式傳輸優化的解析器來逐步顯示內容。

    我們可以從多個來源創建一個流。 例如,您可以讓 service worker構建一個流,其中 shell 來自緩存,而主體來自網絡,而不是提供一個空的 UI shell 並讓 JavaScript 填充它。 正如 Jeff Posnick 所指出的,如果您的 Web 應用程序由 CMS 提供支持,該 CMS 通過將部分模板拼接在一起來呈​​現 HTML,該模型將直接轉換為使用流式響應,模板邏輯複製在服務工作者而不是您的服務器中。 Jake Archibald 的 The Year of Web Streams 文章重點介紹瞭如何構建它。 性能提升非常明顯。

    流式傳輸整個 HTML 響應的一個重要優勢是,在初始導航請求期間呈現的 HTML 可以充分利用瀏覽器的流式 HTML 解析器。 在頁面加載後插入到文檔中的 HTML 塊(這在通過 JavaScript 填充的內容很常見)不能利用這種優化。

    瀏覽器支持? Chrome、Firefox、Safari 和 Edge 的部分支持仍然支持 API 和所有現代瀏覽器都支持的 Service Worker。 如果您再次感到冒險,您可以檢查流式請求的實驗性實現,它允許您在仍然生成正文的同時開始發送請求。 在 Chrome 85 中可用。

一張圖片總結了 Android Chrome 上的保存數據使用情況以及 Cloudinary 研究在 2019 年 11 月和 2020 年 4 月發現的平均 img 點擊或會話
根據 Cloudinary 的研究,全球 18% 的 Android Chrome 用戶啟用了精簡模式(又名 Save-Data)。 (大預覽)
  1. 考慮使您的組件具有連接意識。
    數據可能很昂貴,並且隨著負載的增加,我們需要尊重在訪問我們的網站或應用程序時選擇節省數據的用戶。 Save-Data 客戶端提示請求標頭允許我們為成本和性能受限的用戶定制應用程序和有效負載。

    實際上,您可以將對高 DPI 圖像的請求重寫為低 DPI 圖像、刪除 Web 字體、花哨的視差效果、預覽縮略圖和無限滾動、關閉視頻自動播放、服務器推送、減少顯示項目的數量和降低圖像質量,或者甚至改變你提供標記的方式。 Tim Vereecke 發表了一篇關於 data-s(h)aver 策略的非常詳細的文章,其中包含許多數據保存選項。

    誰在使用save-data ,您可能想知道? 全球 18% 的 Android Chrome 用戶啟用了精簡模式(啟用Save-Data ),而且這個數字可能會更高。 根據 Simon Hearne 的研究,廉價設備的選擇加入率最高,但也有很多異常值。 例如:加拿大用戶的選擇加入率超過 34%(相比之下,美國約為 7%),三星最新旗艦產品的全球用戶選擇加入率接近 18%。

    啟用Save-Data模式後,Chrome Mobile 將提供優化的體驗,即具有延遲腳本、強製font-display: swap和強制延遲加載的代理網絡體驗。 自己構建體驗比依賴瀏覽器進行這些優化更明智。

    當前僅在 Chromium、Android 版本的 Chrome 或桌面設備上的數據保護程序擴展中支持該標頭。 最後,您還可以使用網絡信息 API 來根據網絡類型交付昂貴的 JavaScript 模塊、高分辨率圖像和視頻。 網絡信息 API,特別是navigator.connection.effectiveType使用RTTdownlinkeffectiveType值(以及其他一些值)來提供用戶可以處理的連接和數據的表示。

    在這種情況下,Max Bock 談到了連接感知組件,而 Addy Osmani 談到了自適應模塊服務。 例如,使用 React,我們可以編寫一個針對不同連接類型呈現不同的組件。 正如 Max 所建議的,新聞文章中的<Media />組件可能會輸出:

    • Offline :帶有alt文字的佔位符,
    • 2G / save-data模式:低分辨率圖像,
    • 非 Retina 屏幕上的3G :中等分辨率圖像,
    • Retina 屏幕上的3G :高分辨率 Retina 圖像,
    • 4G :高清視頻。

    Dean Hume 使用服務工作者提供了類似邏輯的實際實現。 對於視頻,我們可以默認顯示視頻海報,然後在更好的連接上顯示“播放”圖標以及視頻播放器外殼、視頻元數據等。 作為不支持瀏覽器的後備方案,我們可以監聽canplaythrough事件並使用Promise.race()如果canplaythrough事件在 2 秒內未觸發,則使源加載超時。

    如果您想更深入地研究,這裡有一些資源可以開始:

    • Addy Osmani 展示瞭如何在 React 中實現自適應服務。
    • React Adaptive Loading Hooks & Utilities 提供了 React 的代碼片段,
    • Netanel Basel 探索 Angular 中的連接感知組件,
    • Theodore Vorilas 分享了在 Vue 中使用網絡信息 API 服務自適應組件的工作原理。
    • Umar Hansa 展示瞭如何選擇性地下載/執行昂貴的 JavaScript。
  2. 考慮讓您的組件具有設備內存感知能力。
    但是,網絡連接只為我們提供了一種關於用戶上下文的視角。 更進一步,您還可以使用設備內存 API 根據可用設備內存動態調整資源。 navigator.deviceMemory返回設備有多少 RAM(以 GB 為單位),向下舍入到最接近的 2 次冪。 該 API 還具有客戶端提示標頭Device-Memory ,它報告相同的值。

    獎勵Umar Hansa 展示瞭如何通過動態導入推遲昂貴的腳本,以根據設備內存、網絡連接和硬件並發性來改變體驗。

從 Chrome 46 及更高版本開始,顯示 Blink 中不同資源的優先級細分
從 Chrome 46 及更高版本開始,顯示 Blink 中不同資源的優先級細分。 (圖片來源:Addy Osmani)(大預覽)
  1. 預熱連接以加快交付速度。
    使用資源提示來節省dns-prefetch (在後台執行 DNS 查找)、 preconnect (要求瀏覽器在後台啟動連接握手(DNS、TCP、TLS))、 prefetch (要求瀏覽器請求資源)和preload (預取資源而不執行資源等)。 在現代瀏覽器中得到很好的支持,很快就會支持 Firefox。

    還記得prerender嗎? 用於提示瀏覽器在後台構建整個頁面以進行下一次導航的資源提示。 實施問題非常成問題,從巨大的內存佔用和帶寬使用到多次註冊的分析點擊和廣告印象。

    不出所料,它已被棄用,但 Chrome 團隊已將其作為 NoState Prefetch 機制恢復。 事實上,Chrome 將prerender提示視為 NoState Prefetch,因此我們今天仍然可以使用它。 正如 Katie Hempenius 在那篇文章中解釋的那樣,“與預渲染一樣, NoState Prefetch 會提前獲取資源;但與預渲染不同的是,它不執行 JavaScript或提前渲染頁面的任何部分。”

    NoState Prefetch 僅使用約 45MiB 的內存,並且提取的子資源將以IDLE Net Priority 進行提取。 從 Chrome 69 開始,NoState Prefetch 添加了 header目的:Prefetch到所有請求,以使它們與正常瀏覽區分開來。

    此外,請注意預渲染替代方案和門戶,這是對隱私意識預渲染的一項新努力,它將為無縫導航提供內容的嵌入preview

    使用資源提示可能是提高性能的最簡單方法,而且確實效果很好。 什麼時候用什麼? 正如 Addy Osmani 所解釋的,預加載我們知道很可能在當前頁面上使用的資源以及未來跨多個導航邊界的導航是合理的,例如用戶尚未訪問的頁面所需的 Webpack 包。

    Addy 關於“在 Chrome 中加載優先級”的文章展示了 Chrome 如何準確地解釋資源提示,因此一旦您確定了哪些資產對渲染至關重要,您就可以為它們分配高優先級。 要查看您的請求的優先級,您可以在 Chrome DevTools 網絡請求表(以及 Safari)中啟用“優先級”列。

    這些天的大部分時間,我們至少會使用preconnectdns-prefetch ,並且我們會謹慎使用prefetchpreloadprerender 。 請注意,即使使用preconnectdns-prefetch ,瀏覽器也會限制並行查找/連接的主機數量,因此可以安全地根據優先級對它們進行排序(感謝 Philip Tellis! )。

    由於字體通常是頁面上的重要資產,有時請求瀏覽器下載帶有preload的關鍵字體是個好主意。 但是,請仔細檢查它是否真的有助於性能,因為在預加載字體時存在優先級難題:由於preload被視為非常重要,它可以超越更關鍵的資源,例如關鍵的 CSS。 (謝謝​​,巴里!

    <!-- Loading two rendering-critical fonts, but not all their weights. --> <!-- crossorigin="anonymous" is required due to CORS. Without it, preloaded fonts will be ignored. https://github.com/w3c/preload/issues/32 via https://twitter.com/iamakulov/status/1275790151642423303 --> <link rel="preload" as="font" href="Elena-Regular.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" /> <link rel="preload" as="font" href="Mija-Bold.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" />
    <!-- Loading two rendering-critical fonts, but not all their weights. --> <!-- crossorigin="anonymous" is required due to CORS. Without it, preloaded fonts will be ignored. https://github.com/w3c/preload/issues/32 via https://twitter.com/iamakulov/status/1275790151642423303 --> <link rel="preload" as="font" href="Elena-Regular.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" /> <link rel="preload" as="font" href="Mija-Bold.woff2" type="font/woff2" crossorigin="anonymous" media="only screen and (min-width: 48rem)" />

    由於<link rel="preload">接受media屬性,您可以選擇根據@media查詢規則有選擇地下載資源,如上所示。

    此外,我們可以使用imagesrcsetimagesizes屬性更快地預加載後期發現的英雄圖像,或者通過 JavaScript 加載的任何圖像,例如電影海報:

    <!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="image" href="poster.jpg" image image>
    <!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="image" href="poster.jpg" image image>

    我們還可以將 JSON 預加載fetch ,以便在 JavaScript 請求它之前發現它:

    <!-- Addy Osmani. https://addyosmani.com/blog/preload-hero-images/ --> <link rel="preload" as="fetch" href="foo.com/api/movies.json" crossorigin>

    我們還可以動態加載 JavaScript,有效地延遲執行腳本。

    /* Adding a preload hint to the head */ var preload = document.createElement("link"); link.href = "myscript.js"; link.rel = "preload"; link.as = "script"; document.head.appendChild(link); /* Injecting a script when we want it to execute */ var script = document.createElement("script"); script.src = "myscript.js"; document.body.appendChild(script);

    需要記住的一些問題: preload有助於將資產的開始下載時間移到更接近初始請求的位置,但預加載的資產會落在與發出請求的頁面相關的內存緩存中。 preload與 HTTP 緩存配合得很好:如果項目已經在 HTTP 緩存中,則永遠不會發送網絡請求。

    因此,它對於後期發現的資源、通過background-image加載的英雄圖像、內聯關鍵 CSS(或 JavaScript)以及預加載其餘的 CSS(或 JavaScript)很有用。

    一個使用湯姆漢克斯主演的灰狗電影封面的例子,顯示預加載的圖像加載得更早,因為不需要等待 JavaScript 來發現
    儘早預加載重要圖像; 無需等待 JavaScript 來發現它們。 (圖片來源:Addy Osmani 的“更快地預加載後期發現的英雄圖像”)(大預覽)

    只有在瀏覽器從服務器接收到 HTML 並且前瞻解析器找到了preload標籤後, preload標籤才能啟動預加載。 通過 HTTP 標頭預加載可能會更快一些,因為我們不需要等待瀏覽器解析 HTML 來啟動請求(儘管有爭議)。

    Early Hints 將進一步提供幫助,甚至可以在發送 HTML 的響應標頭之前啟動預加載(在 Chromium、Firefox 的路線圖上)。 另外,優先級提示將幫助我們指示腳本的加載優先級。

    注意:如果您使用preload as必須定義或不加載任何內容,加上沒有crossorigin屬性的預加載字體將雙重獲取。 如果您使用的是prefetch ,請注意 Firefox 中的Age標頭問題。

顯示在給定時間段內(以毫秒為單位)計數從 0 到 150 的第一個內容繪製(按服務器工作人員狀態)的圖表
使用 service worker,我們可以隻請求最少的數據,然後將這些數據轉換為完整的 HTML 文檔以改進 FCP。 (通過菲爾沃爾頓)(大預覽)
  1. 使用服務工作者進行緩存和網絡回退。
    網絡上的任何性能優化都不會比用戶機器上本地存儲的緩存更快(但也有例外)。 如果您的網站通過 HTTPS 運行,我們可以將靜態資產緩存在 service worker 緩存中並存儲離線回退(甚至離線頁面)並從用戶的機器上檢索它們,而不是通過網絡。

    正如 Phil Walton 所建議的,對於服務工作者,我們可以通過以編程方式生成響應來發送更小的 HTML 有效負載。 Service Worker 可以從服務器請求其所需的最少數據(例如 HTML 內容部分、Markdown 文件、JSON 數據等),然後它可以以編程方式將該數據轉換為完整的 HTML 文檔。 因此,一旦用戶訪問站點並安裝了 service worker,用戶將永遠不會再次請求完整的 HTML 頁面。 性能影響可能相當可觀。

    瀏覽器支持? 服務工作者受到廣泛支持,無論如何,後備是網絡。 它有助於提高性能嗎? 哦,是的,確實如此。 而且它正在變得越來越好,例如 Background Fetch 也允許通過 service worker 進行後台上傳/下載。

    服務工作者有許多用例。 例如,您可以實現“離線保存”功能、處理損壞的圖像、在選項卡之間引入消息傳遞或根據請求類型提供不同的緩存策略。 一般來說,一個常見的可靠策略是將應用程序外殼與一些關鍵頁面一起存儲在服務工作者的緩存中,例如離線頁面、首頁以及對您的情況可能很重要的任何其他內容。

    不過,有一些問題需要牢記。 有了服務工作者,我們需要注意 Safari 中的範圍請求(如果您將 Workbox 用於服務工作者,它有一個範圍請求模塊)。 如果你曾經偶然發現DOMException: Quota exceeded. 瀏覽器控制台出錯,然後查看 Gerardo 的文章When 7KB equals 7MB。

    正如 Gerardo 所寫,“如果您正在構建一個漸進式 Web 應用程序,並且當您的 Service Worker 緩存從 CDN 提供的靜態資產時,您遇到了膨脹的緩存存儲,請確保跨域資源存在正確的 CORS 響應標頭,您不要緩存不透明的響應與您的服務人員無意中,您通過將crossorigin屬性添加到<img>標記來選擇跨域圖像資產進入 CORS 模式。”

    有很多很棒的資源可以幫助您開始使用服務人員:

    • Service Worker Mindset,它可以幫助您了解 Service Worker 如何在幕後工作以及在構建服務人員時要了解的內容。
    • Chris Ferdinandi 提供了一系列關於服務工作者的精彩文章,解釋瞭如何創建離線應用程序並涵蓋了各種場景,從離線保存最近查看的頁面到為服務工作者緩存中的項目設置過期日期。

    • Service Worker 陷阱和最佳實踐,包括一些關於範圍、延遲註冊 Service Worker 和 Service Worker 緩存的提示。
    • Ire Aderinokun 與 Service Worker 的“離線優先”系列,其中包含預緩存應用程序外殼的策略。
    • Service Worker:介紹如何使用 Service Worker 獲得豐富的離線體驗、定期後台同步和推送通知的實用技巧。
    • 總是值得參考優秀的 ol' Jake Archibald 的離線食譜,其中包含許多關於如何烘焙自己的服務人員的食譜。
    • Workbox 是一組專門為構建漸進式 Web 應用程序而構建的服務工作者庫。
  2. 您是否在 CDN/Edge 上運行服務器工作者,例如用於 A/B 測試?
    在這一點上,我們已經習慣了在客戶端運行服務工作者,但是通過在服務器上實現它們的 CDN,我們也可以使用它們來調整邊緣的性能。

    例如,在 A/B 測試中,當 HTML 需要為不同的用戶改變其內容時,我們可以使用 CDN 服務器上的 Service Worker 來處理邏輯。 我們還可以流式傳輸 HTML 重寫以加速使用 Google 字體的網站。

顯示 2016 年 1 月至 2020 年 7 月期間桌面和移動設備上的服務工作者安裝時間序列以及頁面百分比的圖表
Service Worker 安裝的時間序列。 根據 Web Almanac 的數據,只有 0.87% 的桌面頁面註冊了 Service Worker。 (大預覽)
  1. 優化渲染性能。
    每當應用程序運行緩慢時,都會立即引起注意。 因此,我們需要確保在滾動頁面或動畫元素時沒有延遲,並且您始終保持每秒 60 幀的速度。 如果這不可能,那麼至少使每秒幀數保持一致比 60 到 15 的混合範圍更可取。使用 CSS 的will-change通知瀏覽器哪些元素和屬性將發生變化。

    每當您遇到時,請在 DevTools 中調試不必要的重繪

    • 測量運行時渲染性能。 查看一些有關如何理解它的有用提示。
    • 要開始使用,請查看 Paul Lewis 關於瀏覽器渲染優化的免費 Udacity 課程和 Georgy Marchuk 關於瀏覽器繪畫和 Web 性能考慮的文章。
    • 在 Firefox DevTools 的“更多工具 → 渲染 → 畫圖閃爍”中啟用畫圖閃爍。
    • 在 React DevTools 中,選中“突出顯示更新”並啟用“記錄每個組件呈現的原因”,
    • 您還可以使用Why Did You Render,因此當重新渲染組件時,Flash 會通知您更改。

    您使用的是砌體佈局嗎? 請記住,很快就可以單獨使用 CSS 網格構建 Masonry 佈局。

    如果您想深入探討該主題,Nolan Lawson 在他的文章中分享了準確測量佈局性能的技巧,Jason Miller 也提出了替代技術。 我們還有一篇 Sergey Chikuyonok 撰寫的關於如何正確設置 GPU 動畫的文章。

    高性能動畫,包括位置、縮放、旋轉和不透明度
    瀏覽器可以廉價地為變換和不透明度設置動畫。 CSS 觸發器對於檢查 CSS 是否觸發重新佈局或重排很有用。 (圖片來源:Addy Osmani)(大預覽)

    注意:對 GPU 合成層的更改是最便宜的,因此如果您可以通過opacitytransform僅觸發合成來擺脫困境,那麼您將走在正確的軌道上。 Anna Migas 在她關於調試 UI 渲染性能的演講中也提供了很多實用的建議。 要了解如何在 DevTools 中調試繪製性能,請查看 Umar 的繪製性能審核視頻。

  2. 您是否針對感知性能進行了優化?
    雖然組件出現在頁面上的順序,以及我們如何為瀏覽器提供資源的策略很重要,但我們也不應該低估感知性能的作用。 這個概念涉及等待的心理方面,基本上是讓客戶在其他事情發生時保持忙碌或參與。 這就是感知管理、搶先開始、提前完成和容忍管理髮揮作用的地方。

    這是什麼意思呢? 在加載資產時,我們可以嘗試始終領先於客戶一步,因此在後台發生很多事情的同時,體驗感覺很快。 為了保持客戶的參與度,我們可以測試骨架屏幕(實現演示)而不是加載指示器,添加過渡/動畫,並且基本上在沒有什麼可以優化的情況下欺騙用戶體驗。

    在他們關於 UI 骨架藝術的案例研究中,Kumar McMillan 分享了一些關於如何模擬動態列表、文本和最終屏幕的想法和技術,以及如何使用 React 考慮骨架思維

    但請注意:在部署之前應該測試骨架屏幕,因為一些測試表明骨架屏幕在所有指標上都表現最差。

  3. 您是否防止佈局變化和重繪?
    在感知性能領域,可能更具破壞性的體驗之一是佈局轉換回流,這是由重新縮放的圖像和視頻、網絡字體、注入的廣告或用實際內容填充組件的後期發現的腳本引起的。 結果,客戶可能開始閱讀一篇文章,只是被閱讀區域上方的佈局跳轉打斷。 這種體驗通常是突然的並且非常令人迷惑:這可能是加載需要重新考慮的優先級的情況。

    社區已經開發了一些技術和解決方法來避免回流。 一般來說,避免在現有內容之上插入新內容是個好主意,除非它是為了響應用戶交互而發生的。 始終在圖像上設置寬度和高度屬性,因此現代瀏覽器默認分配框並保留空間(Firefox、Chrome)。

    對於圖像或視頻,我們可以使用 SVG 佔位符來保留媒體將出現的顯示框。這意味著當您需要保持其縱橫比時,該區域將被正確保留。 我們還可以為廣告和動態內容使用佔位符或後備圖像,以及預分配佈局插槽。

    僅在不支持本地延遲加載時才加載外部腳本時,考慮使用本地延遲加載或混合延遲加載,而不是使用外部腳本延遲加載圖像。

    如上所述,始終將 Web 字體重繪和從所有後備字體轉換為所有Web 字體一次 - 只需確保該切換不會太突然,通過使用 font-style-matcher 調整字體之間的行高和間距.

    覆蓋備用字體的字體指標以模擬 Web 字體,我們可以使用 @font-face 描述符來覆蓋字體指標(演示,在 Chrome 87 中啟用)。 (請注意,調整會因複雜的字體堆棧而變得複雜。)

    對於後期 CSS,我們可以確保佈局關鍵的 CSS 內聯在每個模板的標題中。 更進一步:對於長頁面,當添加垂直滾動條時,它會將主要內容向左移動 16px。 為了儘早顯示滾動條,我們可以在html上添加overflow-y: scroll以在第一次繪製時強制滾動條。 後者有幫助,因為當寬度改變時,由於折疊內容重排,滾動條會導致非平凡的佈局變化。 不過,應該主要發生在具有非覆蓋滾動條的平台上,例如 Windows。 但是:中斷position: sticky ,因為這些元素永遠不會滾動出容器。

    如果您處理在滾動時固定或粘在頁面頂部的頁眉,請在頁眉鬆動時為頁眉保留空間,例如在內容上使用佔位符元素或margin-top 。 一個例外應該是不應該對 CLS 產生影響的 cookie 同意橫幅,但有時他們會這樣做:這取決於實施。 在這個 Twitter 線程中有一些有趣的策略和要點。

    對於可能包含大量文本的選項卡組件,您可以使用 CSS 網格堆棧來防止佈局變化。 通過將每個選項卡的內容放在同一個網格區域,並一次隱藏其中一個,我們可以確保容器始終採用較大元素的高度,因此不會發生佈局偏移。

    啊,當然,如果列表下方有內容(例如頁腳),無限滾動和“加載更多”也會導致佈局變化。 要改進 CLS,請在用戶滾動到頁面的該部分之前為將要加載的內容保留足夠的空間,刪除頁腳或頁面底部可能因內容加載而被壓下的任何 DOM 元素。此外,為首屏內容預取數據和圖像,這樣當用戶滾動到那麼遠時,它就已經存在了。 您也可以使用 react-window 之類的列表虛擬化庫來優化長列表(感謝 Addy Osmani! )。

    為確保包含回流的影響,請使用 Layout Instability API 測量佈局穩定性。 有了它,您可以計算 Cumulative Layout Shift ( CLS ) 分數並將其作為測試要求包含在內,因此每當出現回歸時,您都可以對其進行跟踪和修復。

    為了計算佈局偏移分數,瀏覽器查看視口大小和兩個渲染幀之間的視口中不穩定元素的移動。 理想情況下,分數將接近0 。 Milica Mihajlija 和 Philip Walton 就 CLS 是什麼以及如何測量它提供了一個很棒的指南。 這是衡量和維護感知性能並避免中斷的良好起點,尤其是對於關鍵業務任務。

    快速提示:要了解導致 DevTools 中佈局變化的原因,您可以在性能面板的“體驗”下探索佈局變化。

    獎勵:如果您想減少重排和重繪,請查看 Charis Theodoulou 的《最小化 DOM 重排/佈局抖動指南》和 Paul Irish 的強制佈局/重排的列表以及 CSSTriggers.com,這是一個關於觸發佈局、繪製的 CSS 屬性的參考表和合成。

目錄

  1. 準備:計劃和指標
  2. 設定切合實際的目標
  3. 定義環境
  4. 資產優化
  5. 構建優化
  6. 交付優化
  7. 網絡、HTTP/2、HTTP/3
  8. 測試和監控
  9. 快速獲勝
  10. 一切都在一頁上
  11. 下載清單(PDF、Apple Pages、MS Word)
  12. 訂閱我們的電子郵件通訊不要錯過下一個指南。