日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

浏览器性能优化实战

發(fā)布時間:2024/2/28 HTML 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浏览器性能优化实战 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

作者:rosefang,騰訊 PCG 前端開發(fā)工程師

當(dāng)我們在做性能優(yōu)化的時候,我們究竟在優(yōu)化什么?瀏覽器底層是一個什么架構(gòu)?瀏覽器渲染的本質(zhì)究竟是什么?哪些方面對用戶的體驗影響才是最大的?有沒有業(yè)內(nèi)一些通用的標(biāo)準(zhǔn)或標(biāo)桿參考?都 1202 年了,雅虎軍規(guī)還有沒有用?性能分析工具都有哪些?我們怎么進(jìn)行打點分析才是合適的?

本文為你一一講解這些。了解了這些問題,可能你在做性能優(yōu)化的時候才能更加得心應(yīng)手。

1. 性能優(yōu)化的本質(zhì)

1.1 展示更快,響應(yīng)更快

性能優(yōu)化的目的,就是為了提供給用戶更好的體驗,這些體驗包含這幾個方面:展示更快、交互響應(yīng)快、頁面無卡頓情況。

更詳細(xì)的說,就是指,在用戶輸入 url 到站點完整把整個頁面展示出來的過程中,通過各種優(yōu)化策略和方法,讓頁面加載更快;在用戶使用過程中,讓用戶的操作響應(yīng)更及時,有更好的用戶體驗。

對于前端工程師來說,要做好性能優(yōu)化,需要理解瀏覽器加載和渲染的本質(zhì)。理解了本質(zhì)原理,才能更好的去做優(yōu)化。所以我們先來看看瀏覽器架構(gòu)是怎樣的。

1.2 理解瀏覽器多進(jìn)程架構(gòu)

從大的方面來說,瀏覽器是一個多進(jìn)程架構(gòu)。

它可以是一個進(jìn)程包含多個線程,也可以是多個進(jìn)程中,每個進(jìn)程有多個線程,線程之間通過 IPC 通訊。每個瀏覽器有不同的實現(xiàn)細(xì)節(jié),并沒有標(biāo)準(zhǔn)規(guī)定瀏覽器必須如何去實現(xiàn)。

這里我們只談?wù)?chrome 架構(gòu)。

下面這張圖是目前 chrome 的多進(jìn)程架構(gòu)圖。

圖片引自 Mariko Kosaka 的《Inside look at modern web browser》

我們來看看這些進(jìn)程分別對應(yīng)瀏覽器窗口中的哪一部分:

圖片引自 Mariko Kosaka 的《Inside look at modern web browser》

那么,怎么看瀏覽器對應(yīng)啟動了什么進(jìn)程呢?

chrome 中,我們可以通過更多->More Tools->Task Manager 看到啟動的進(jìn)程。

從 chrome 官網(wǎng)和源碼,我們也可以得知,多進(jìn)程架構(gòu)中包含這些進(jìn)程:

  • Browser 進(jìn)程:打開瀏覽器后,始終只有一個。該進(jìn)程有 UI 線程、Network 線程、Storage 線程等。用戶輸入 url 后,首先是 Browser 進(jìn)程進(jìn)行響應(yīng)和請求服務(wù)器獲取數(shù)據(jù)。然后傳遞給 Renderer 進(jìn)程。

  • Renderer 進(jìn)程:每一個 tab 一個,負(fù)責(zé) html、css、js 執(zhí)行的整個過程。前端性能優(yōu)化也與這個進(jìn)程有關(guān)。

  • Plugin 進(jìn)程:與瀏覽器插件相關(guān),例如 flash 等。

  • GPU 進(jìn)程:瀏覽器共用一個。主要負(fù)責(zé)把 Renderer 進(jìn)程中繪制好的 tile 位圖作為紋理上傳到 GPU,并調(diào)用 GPU 相關(guān)方法把紋理 draw 到屏幕上。

這里的話只是簡單介紹一下瀏覽器的多進(jìn)程架構(gòu),讓大家對瀏覽器整體架構(gòu)有個初步認(rèn)識,其實背后的細(xì)節(jié)還有很多,這里就不一一展開。有興趣可以細(xì)看這一系列文章和chrome 官網(wǎng)介紹。

1.3 理解頁面渲染相關(guān)進(jìn)程

1.3.1 Renderer Process & GPU Process

從以上的多架構(gòu),我們了解到,與前端渲染、性能優(yōu)化相關(guān)的,其實主要是 Renderer 進(jìn)程和 GPU 進(jìn)程。那么,它們又是什么架構(gòu)呢?

來看一下這張我們再熟悉不過的圖。

圖片引自 Paul 的《The Anatomy of a Frame》
  • Renderer 進(jìn)程:包括 3 個線程。合成線程(Compositor Thread)、主線程(Main Thread)、Compositor Tile Worker。

  • GPU 進(jìn)程:只有 GPU 線程,負(fù)責(zé)接收從 Renderer 進(jìn)程中的 Compositor Thread 傳過來的紋理,顯示到屏幕上。

1.3.2 Renderer Process 詳解

Renderer 進(jìn)程中 3 個線程的作用為:

  • Compositor Thread:首先接收 vsync 信號(vsync 信號是指操作系統(tǒng)指示瀏覽器去繪制新的幀),任何事件都會先到達(dá) Compositor 線程。如果主線程沒有綁定事件,那么 Compositor 線程將避免進(jìn)入主線程,并嘗試將輸入轉(zhuǎn)換為屏幕上的移動。它將更新的圖層位置信息作為幀通過 GPU 線程傳遞給 GPU 進(jìn)行繪制。

當(dāng)用戶在快速滑動過程中,如果主線程沒有綁定事件,Compositor 線程是可以快速響應(yīng)并繪制的,這是瀏覽器做的一個優(yōu)化。

  • Main Thread:主線程就是我們前端工程師熟知的線程,這里會執(zhí)行解析 Html、樣式計算、布局、繪制、合成等動作。所以關(guān)于性能的問題,都發(fā)生在了這里。所以應(yīng)該重點關(guān)注這里

  • Compositor Tile Worker:由合成線程產(chǎn)生一個或多個 worker 來處理光柵化的工作。

Service Workers 和 Web Workers 可以暫時理解也在 Renderer 進(jìn)程中,這里不展開討論。

1.3.2.1 Main Thread
main-thread

主線程需要重點講下。因為這是我們的代碼真實存在的環(huán)境。

從上一小節(jié) Render 進(jìn)程和 GPU 進(jìn)程的圖中,我們可以看到有個紅色的箭頭,從 Recal Styles 和 Layout 指向了 requestAnimationFrame,這意味著有 Forced Synchronous Layout (or Styles)(強(qiáng)制回流和重繪)發(fā)生,這一點在性能方面特別要注意。

在 Main Thread 中,有這幾個需要注意一下:

  • requestAnimationFrame:因為布局和樣式計算是在 rAF 之后,所以在 rAF 是進(jìn)行元素變更的理想時機(jī)。如果在這里對一個元素變更 100 個類,不會進(jìn)行 100 次計算,它們會分批以后處理。需要注意的是,不能在 rAF 中查詢?nèi)魏斡嬎銟邮胶筒季值膶傩?#xff08;例如:el.style.backgroundImage 或 el.style.offsetWidth),因為這樣會導(dǎo)致重繪和回流。

  • Layout:布局的計算通常是針對整個文檔的,并且與 DOM 元素的大小成正比!(這點特別要注意,如果一個頁面 DOM 元素太多,也會導(dǎo)致性能問題)

主線程的順序始終都是:

Input?Event?Handler->requestAnimationFrame->ParseHtml->ReculateStyles->Layout-?>Update?Layer?Tree->Paint->Composite->commit->requestIdleCallback

只能從前往后,例如,必須先是 ReculateStyles,然后 Layout、然后 Paint。但是,如果它只需要做最后一步 Paint,那么這就是它全部要做的事情,不會再發(fā)生前面的 ReculateStyles 和 Layout。

這里其實給了我們一個啟示:如果要讓 fps 保持 60,即每幀的 js 執(zhí)行時間少于 16.66ms,那么讓這個主線程執(zhí)行的過程盡可能地少,是我們的性能優(yōu)化目標(biāo)。

根據(jù)主線程的這些步驟,理想的情況下,我們只希望瀏覽器只發(fā)生最后一個步驟:Composite(合成)。

CSS 的屬性是我們需要關(guān)注一下的模塊。這里有描述了哪些CSS 屬性會引起重繪、回流和合成。例如,讓我們給一個元素進(jìn)行移動位置時:transform和opacity可以直接觸發(fā)合成,但是left和top卻會觸發(fā) Layout、Paint、Composite3 個動作。所以顯然用 transform 時更好的方案。

但這并不是說我們不應(yīng)該用 left 和 top 這些可能引起重繪回流的屬性,而是應(yīng)該關(guān)注每個屬性在瀏覽器性能中引起的效果。

2. 看看經(jīng)典:雅虎軍規(guī)

多年前雅虎的 Nicolas C. Zakas 提出 7 個類別 35 條軍規(guī),至今為止很多前端優(yōu)化準(zhǔn)則都是圍繞著這個展開。如果嚴(yán)格按照這些規(guī)則去做,其實我們有很多優(yōu)化工作可以做,只要認(rèn)真踐行,性能提升不是問題。

我們來看看它 7 個分類都是圍繞哪些方面展開:

  • Server:與頁面發(fā)起請求的相關(guān);

  • Cookie:與頁面發(fā)起請求相關(guān);

  • Mobile:與頁面請求相關(guān);

  • Content:與頁面渲染相關(guān);

  • Image:與頁面渲染相關(guān);

  • CSS:與頁面渲染相關(guān);

  • Javascript:與頁面渲染和交互相關(guān)。

從上面的描述可以看到,其實雅虎軍規(guī),是圍繞頁面發(fā)起請求那一刻,到頁面渲染完成,頁面開始交互這幾個方面來展開,提出的一些原則。

很多原則大家也都耳熟能詳,就不全部展開了,有興趣的同學(xué)可以去查看原文。這里主要想提一些忽略但是又值得注意的點:

減少 DOM 節(jié)點數(shù)量

為什么要減少 DOM 節(jié)點的數(shù)量?

當(dāng)遍歷查詢 500 和 5000 個 DOM 節(jié)點,進(jìn)行事件綁定時,會有所差別。

當(dāng)一個頁面 DOM 節(jié)點過多,應(yīng)該考慮使用無限滾動方案來使視窗節(jié)點可控??梢钥纯磄oogle 提的方案。

減少 cookie 大小

cookie 傳輸會造成帶寬浪費,影響響應(yīng)時間,可以這樣做:

消除不必要的 cookies;

靜態(tài)資源不需要 cookie,可以采用其他的域名,不會主動帶上 cookie。

避免圖片 src 為空

圖片 src 為空時,不同瀏覽器會有不同的副作用,會重新發(fā)起一起請求。

3. 性能指標(biāo)

3.1 什么樣的性能指標(biāo)才能真正代表用戶體驗?

要衡量性能,我們必須有一些客觀的、可衡量的指標(biāo)來進(jìn)行監(jiān)控。但是客觀且定量可衡量的指標(biāo)不一定能反映用戶的真實體驗。

以前,我們會用 load 事件的觸發(fā)來衡量一個頁面是否加載或顯示完成。但是設(shè)想會不會有這樣的情況:一個頁面的 load 事件已經(jīng)被觸發(fā),但是卻在 load 事件之后幾秒才開始加載內(nèi)容和渲染頁面,所以這個時候,load 事件并不能真實反映用戶看到內(nèi)容的時刻。

在過去幾年,google 團(tuán)隊和W3C 性能工作組致力于提供標(biāo)準(zhǔn)的性能 API 來真正衡量用戶的體驗。主要是從這 4 個方面思考:

思考點詳細(xì)內(nèi)容
Is it happening?導(dǎo)航是否成功,服務(wù)器是否響應(yīng)了
Is it useful?是否已經(jīng)渲染了足夠的內(nèi)容,讓用戶可以開始參與其中
Is it usable?用戶是否可以與頁面交互,頁面是否處于繁忙狀態(tài)
Is it delightful?交互是否流暢、自然、沒有滯后反映或卡頓

通常有 2 種途徑來衡量性能。

  • 本地實驗衡量:本地模擬用戶的網(wǎng)絡(luò)、設(shè)備等情況進(jìn)行測試。通常在開發(fā)新功能的時候,實驗測量是很重要的,因為我們不知道這個功能發(fā)布到線上會有什么性能問題,所以提前進(jìn)行性能測試,可以進(jìn)行預(yù)防。

  • 線上衡量:實驗測量固然可以反映一些問題,但無法反映在用戶那里真實的情況。同樣的,在用戶那里,性能問題會和用戶的設(shè)備、網(wǎng)絡(luò)情況有關(guān),而且還跟用戶如何與頁面進(jìn)行交互有關(guān)。

  • 有這幾個類型與用戶感知性能相關(guān)。

    • 頁面加載時間:頁面以多快的速度加載和渲染元素到頁面上。

    • 加載后響應(yīng)時間:頁面加載和執(zhí)行 js 代碼后多久能響應(yīng)用戶交互。

    • 運行時響應(yīng):頁面加載完成后,對用戶的交互響應(yīng)時間。

    • 視覺穩(wěn)定性:頁面元素是否會以用戶不期望的方式移動,并干擾用戶的交互。

    • 流暢度:過渡和動畫是否以一致的幀率渲染,并從一種狀態(tài)流暢地過渡到另一種狀態(tài)。

    對應(yīng)上面幾種分類,Google 和 W3C 性能工作組提供了對應(yīng)這幾種性能指標(biāo):

    • First contentful paint (FCP): 測量頁面開始加載到某一塊內(nèi)容顯示在頁面上的時間。

    • Largest contentful paint (LCP): 測量頁面開始加載到最大文本塊內(nèi)容或圖片顯示在頁面中的時間。

    • First input delay (FID): 測量用戶首次與網(wǎng)站進(jìn)行交互(例如點擊一個鏈接、按鈕、js 自定義控件)到瀏覽器真正進(jìn)行響應(yīng)的時間。

    • Time to Interactive (TTI): 測量從頁面加載到可視化呈現(xiàn)、頁面初始化腳本已經(jīng)加載,并且可以可靠地快速響應(yīng)用戶的時間。

    • Total blocking time (TBT): 測量從 FCP 到 TTI 之間的時間,這個時間內(nèi)主線程被阻塞無法響應(yīng)用戶輸入。

    • Cumulative layout shift (CLS): 測量從頁面開始加載到狀態(tài)變?yōu)殡[藏過程中,發(fā)生不可預(yù)期的 layout shifts 的累積分?jǐn)?shù)。

    這些指標(biāo)能從一定程度上衡量頁面性能,但不一定都是有效的。舉個例子。LCP 指標(biāo)主要用戶衡量頁面的主要內(nèi)容是否完成加載,但會有這樣的情況,最大的元素并不是主要內(nèi)容,那么這個時候 LCP 指標(biāo)并不是那么重要。

    每個不同的站點有自己的特殊性,可以參考以上角度進(jìn)行衡量,也需要因地制宜。

    3.2 Core Web Vitals

    在以上列出的指標(biāo)中,Google 定義了 3 個最核心的指標(biāo),作為 Core Web Vitals。它們分別代表著:加載、交互、視覺穩(wěn)定性。

    image-20210426192204425
    • Largest Contentful Paint (LCP): 測量加載性能。為了能提供較好的用戶體驗,LCP 指標(biāo)建議頁面首次加載要在 2.5s 內(nèi)完成。

    • First Input Delay (FID): 測量交互性能。為了提供較好用戶體驗,交互時間建議在 100ms 或以內(nèi)。

    • Cumulative Layout Shift (CLS): 測量視覺穩(wěn)定性。為了提供較好用戶體驗,頁面應(yīng)該維持 CLS 在 0.1 或以內(nèi)。

    當(dāng)頁面訪問量有 75%的數(shù)據(jù)達(dá)到了以上以上 Good 的標(biāo)準(zhǔn),則認(rèn)為性能是不錯的了。

    Core Web Vitals 是作為核心性能指標(biāo),但是其他指標(biāo)也同樣在重要,是做為核心指標(biāo)的一個輔助。例如,TTFB 和 FCP 都可以用來衡量加載性能(服務(wù)器響應(yīng)時間和渲染時間),它們作為 LCP 的一個問題手段輔助。同樣的,TBT 和 TTI 對于衡量交互性能也很重要,是 FID 的一個輔助,但是它們無法在線上進(jìn)行測量,也無法反映以用戶為中心的結(jié)果。

    Google 官方提供了一個web-vitals庫,線上或本地都可以測量上面提到的 3 個指標(biāo):

    import?{getCLS,?getFID,?getLCP}?from?'web-vitals';function?sendToAnalytics(metric)?{const?body?=?JSON.stringify(metric);//?Use?`navigator.sendBeacon()`?if?available,?falling?back?to?`fetch()`.(navigator.sendBeacon?&&?navigator.sendBeacon('/analytics',?body))?||fetch('/analytics',?{body,?method:?'POST',?keepalive:?true}); }getCLS(sendToAnalytics); getFID(sendToAnalytics); getLCP(sendToAnalytics);

    下面,分別講講這 3 個指標(biāo)定義的原因、如何測量、如何優(yōu)化。

    3.2.1 Largest Contentful Paint (LCP)
    3.2.1.1 LCP 如何定義
    圖片來自LCP

    LCP 是指頁面開始加載到最大文本塊內(nèi)容或圖片顯示在頁面中的時間。那么哪些元素可以被定義為最大元素呢?

    • <img>標(biāo)簽

    • <image> 在 svg 中的 image 標(biāo)簽

    • <video> video 標(biāo)簽

    • CSS background url()加載的圖片

    • 包含內(nèi)聯(lián)或文本的塊級元素

    3.2.1.2 如何測量 LCP

    線上測量工具

    • Chrome User Experience Report

    • PageSpeed Insights

    • Search Console (Core Web Vitals report)

    • web-vitals JavaScript library

    實驗室工具

    • Chrome DevTools

    • Lighthouse

    • WebPageTest

    原生的 JS API 測量

    LCP 還可以用 JS API 進(jìn)行測量,主要使用 PerformanceObserver 接口,目前除了 IE 不支持,其他瀏覽器基本都支持了。

    new?PerformanceObserver((entryList)?=>?{for?(const?entry?of?entryList.getEntries())?{console.log('LCP?candidate:',?entry.startTime,?entry);} }).observe({type:?'largest-contentful-paint',?buffered:?true});

    我們看一下結(jié)果是怎樣的:

    LCP-example

    Google 官方 web-vitals 庫

    Google 官方也提供了一個web-vitals庫,底層還是使用這個 API,只是幫我們處理了一些需要測量和不需測量的場景、以及一些細(xì)節(jié)問題。

    3.2.1.3 如何優(yōu)化 LCP

    LCP 可能被這四個因素影響:

    • 服務(wù)端響應(yīng)時間

    • Javascript 和 CSS 引起的渲染卡頓

    • 資源加載時間

    • 客戶端渲染

    更加詳細(xì)的優(yōu)化建議就不展開了,可以參考這里。

    3.2.2 First Input Delay (FID)
    3.2.2.1 FID 如何定義
    圖片來自FID

    我們都知道第一印象的重要性,比如初次遇到某人形成的印象,會在后續(xù)交往中起重要的影響。對于一個網(wǎng)站也是如此。

    網(wǎng)站以多快的速度加載完成是其中一項指標(biāo),加載后以多快的速度對用戶進(jìn)行響應(yīng)也同樣重要。FID 就是指后者。

    可以通過下面的圖來更詳細(xì)了解 FID 處于哪個位置:

    圖片來自FID

    從上圖可以看出,當(dāng)主線程處于繁忙的時候,FID 是指從瀏覽器接收到了用戶輸入,到瀏覽器對用戶的輸入進(jìn)行響應(yīng)的延遲時間。

    通常,當(dāng)我們在寫代碼的時候,會認(rèn)為只要用戶輸入信息,我們的事件回調(diào)就會立刻響應(yīng),但實際上并不是這樣。這是主線程可能處于繁忙,瀏覽器正忙著解析和執(zhí)行其他 js。如上圖所示的 FID 時間,主線程正在處理其他任務(wù)。

    當(dāng) FID 的時間為 100ms 或以內(nèi),則為 Good。

    上面的例子中,用戶剛好在主線程最繁忙的時刻進(jìn)行了交互,但是如果用戶在主線程空閑的時候交互,那么瀏覽器可以立刻響應(yīng)。所以 FID 的值需要重點查看它的分布情況。

    FID 實際上測量的是輸入事件被感知到到主線程空閑的這段時間。這意味著即使沒有輸入事件被注冊,FID 也可以測量。因為用戶的輸入相應(yīng)并不一定需要事件被執(zhí)行,但一定需要主線程是空閑的。例如,下面這些 HTML 元素都需要在交互響應(yīng)之前等待主線程上的正在執(zhí)行的任務(wù)完成:

    • 輸入框,例如<input>、<textarea>、<radio>、<checkbox>

    • 下拉框,例如<select>

    • 鏈接,例如<a>

    為什么要考慮測量第一次的輸入延遲?有如下原因:

    • 因為第一次輸入延遲是用戶對你的網(wǎng)站形成的第一個印象,網(wǎng)站是否有質(zhì)量且可靠;

    • 在今天,web 中最大的交互問題第一次加載之后;

    • 對于網(wǎng)站應(yīng)該如何解決較高的首次輸入延遲(例如代碼分割、減少 JavaScript 的預(yù)加載)的建議解決方案(TTI 是指衡量這一塊),不一定與在頁面加載后解決輸入延遲(FID 是指衡量這一塊)的解決方案相同。所以 FID 是在 TTI 的基礎(chǔ)上更精確的細(xì)分。

    為什么 FID 只是包含從用戶輸入到主線程開始相應(yīng)的時間?而沒有包含事件處理到瀏覽器繪制 UI 的時間?

    盡管主線程處理和繪制的這段時間也很重要,但是如果 FID 把這段時間也包含進(jìn)來,開發(fā)者可能會使用異步 API(例如setTimeout、requestAnimationFrame)來把這個 task 拆分到下一幀,以較少 FID 的時間,這樣不僅沒有提高用戶體驗,反而使用戶體驗降低。

    3.2.2.2 如何測量 FID

    FID 可以在實驗環(huán)境也可以在線上環(huán)境測量。

    線上測量工具

    • Chrome User Experience Report

    • PageSpeed Insights

    • Search Console (Core Web Vitals report)

    • web-vitals JavaScript library

    原生的 JS API 測量

    new?PerformanceObserver((entryList)?=>?{for?(const?entry?of?entryList.getEntries())?{const?delay?=?entry.processingStart?-?entry.startTime;console.log('FID?candidate:',?delay,?entry);} }).observe({type:?'first-input',?buffered:?true});

    PerformanceObserver 目前除了在 IE 上沒有兼容,其他瀏覽器基本都兼容了。

    我們看一下結(jié)果是怎樣的:

    FID-example

    Google 官方 web-vitals 庫

    Google 官方也提供了一個web-vitals庫,底層還是使用這個 API,只是幫我們處理了一些需要測量和不需測量的場景、以及一些細(xì)節(jié)問題。

    3.2.2.3 如何優(yōu)化 FID

    FID 可能被這四個因素影響:

    • 減少第三方代碼的影響

    • 減少 Javascript 的執(zhí)行時間

    • 最小化主線程工作

    • 減小請求數(shù)量和請求文件大小

    更加詳細(xì)的優(yōu)化建議可以參考這里。

    3.2.3 Cumulative Layout Shift (CLS)
    3.2.3.1 CLS 如何定義
    圖片來自CLS

    CLS 是一個非常重要的、以用戶為中心的測量指標(biāo)。它能衡量頁面是否排版穩(wěn)定。

    頁面移動會經(jīng)常發(fā)生在資源異步加載、或者 DOM 元素動態(tài)添加到已存在的頁面元素上面。這些元素有可能是圖片、視頻、第三方廣告或小圖標(biāo)等。

    但是我們開發(fā)過程中可能不會察覺到這些問題,因為調(diào)試過程中刷新頁面,圖片都已經(jīng)緩存在本地。調(diào)試接口的時候我們使用的是 mock 或者在局域網(wǎng),接口速度都很快,這些延遲都可能被我們忽略。

    CLS 就是幫我們?nèi)グl(fā)現(xiàn)這些真實發(fā)生在用戶端的問題的指標(biāo)。

    CLS 是測量頁面生命周期中,每個發(fā)生意外布局移動的分?jǐn)?shù)。當(dāng)一個可視元素在下一幀移動到另外一個位置,就是指布局移動。

    CLS 的分?jǐn)?shù)在 0.1 或以下,則為 Good。

    那么意外布局移動的分?jǐn)?shù)如何計算?

    瀏覽器會監(jiān)控兩楨之間發(fā)生移動的不穩(wěn)定元素。布局移動分?jǐn)?shù)由 2 個元素決定:impact fraction 和 distance fraction。

    layout?shift?score?=?impact?fraction?*?distance?fraction

    可視區(qū)域內(nèi),在前一幀到下一幀之間所有不穩(wěn)定的元素的并集,會影響當(dāng)前幀的布局移動分?jǐn)?shù)。

    舉個例子,下面這張圖中,左邊是當(dāng)前幀的一個元素,下一幀中,元素下移了可視區(qū)域內(nèi) 25%的高度。紅色虛線框標(biāo)出了兩楨中當(dāng)前元素的并集,占適口的 75%,所以這個時候,impact faction 是 0.75。

    另外一個影響布局移動分?jǐn)?shù)的是 distance fraction,指這個元素相對視口移動的距離。不管是橫向還是豎向,取最大值。

    下面例子中,豎向距離更大,該元素相對適口移動了 25%的距離,所以 distance fraction 是 0.25。所以布局移動分?jǐn)?shù)是 0.75 * 0.25 = 0.1875.

    impact-fraction-example

    但是要注意的是,并不是所有的布局移動都是不好的,很多 web 網(wǎng)站都會改變元素的開始位置。只有當(dāng)布局移動是非用戶預(yù)期的,才是不好的。

    換句話說,當(dāng)用戶點擊了按鈕,布局進(jìn)行了改動,這是 ok 的,CLS 的 JS API 中有一個字段hadRecentInput,用來標(biāo)識 500ms 內(nèi)是否有用戶數(shù)據(jù),視情況而定,可以忽略這個計算。

    3.2.3.2 如何測量 CLS

    線上測量工具

    • Chrome User Experience Report

    • PageSpeed Insights

    • Search Console (Core Web Vitals report)

    • web-vitals JavaScript library

    實驗室工具

    • Chrome DevTools

    • Lighthouse

    • WebPageTest

    原生的 JS API 測量

    let?cls?=?0;new?PerformanceObserver((entryList)?=>?{for?(const?entry?of?entryList.getEntries())?{if?(!entry.hadRecentInput)?{cls?+=?entry.value;console.log('Current?CLS?value:',?cls,?entry);}} }).observe({type:?'layout-shift',?buffered:?true});

    我們看一下結(jié)果是怎樣的:

    CLS-example

    Google 官方 web-vitals 庫

    Google 官方也提供了一個web-vitals庫,底層還是使用這個 API,只是幫我們處理了一些需要測量和不需測量的場景、以及一些細(xì)節(jié)問題。

    3.2.3.3 如何優(yōu)化 CLS

    我們可以根據(jù)這些原則來避免非預(yù)期布局移動:

    • 圖片或視屏元素有大小屬性,或者給他們保留一個空間大小,設(shè)置 width、height,或者使用unsized-media feature policy。

    • 不要在一個已存在的元素上面插入內(nèi)容,除了相應(yīng)用戶輸入。

    • 使用 animation 或 transition 而不是直接觸發(fā)布局改變。

    更詳細(xì)的內(nèi)容可以看這里。

    4. 性能工具:工欲善其事,必先利其器

    Google 開發(fā)的所有工具都支持 Core Web Vitals 的測量。工具如下:

    • Lighthouse

    • PageSpeed Insights

    • Chrome DevTools

    • Search Console

    • web.dev's 提供的測量工具

    • Web Vitals 擴(kuò)展

    • Chrome UX Report API

    tools-all

    這些工具對 Core Web Vitals 的支持如下:

    tools

    4.1 Lighthouse

    打開 F12,就可以看到 Lighthouse,點擊 Generate Report,即可生成報告。當(dāng)然也可以添加 chrome 插件使用。

    lighthouse

    Lighthhouse 是一個實驗室工具,本地模擬移動端和 PC 端對這幾個方面進(jìn)行測試。同時 lighthouse 還會針對這幾個方面提出建議,在產(chǎn)品上線前值得一測。

    lighthouse-func

    Lighthouse 還提供了Lighthouse CI,把 Lighthouse 集成到 CI 流水線中。舉個例子,每次在上線之前,跑 50 次流水線對 Lighthouse 的各項指標(biāo)進(jìn)行測試取平均值,一旦發(fā)現(xiàn)異常,立刻進(jìn)行排查。把性能問題排查提前到發(fā)布之前。這塊后面會細(xì)講。

    4.2 PageSpeed Insights

    PageSpeed Insights(PSI)是一個可以分析線上和實驗室數(shù)據(jù)的工具。它是根據(jù)線上環(huán)境用戶真實的數(shù)據(jù)(在 Chrome UX 報告中)和 Lighthouse 結(jié)合出一份報告。和 Lighthouse 類似,它也會給出一些分析建議,可以知道頁面的 Core Web Vitals 是否達(dá)標(biāo)。

    PageSpeed-demo

    PageSpeed 只是提供對單個頁面的性能測試,而 Search Console 是正對整個網(wǎng)站的性能測試。

    PageSpeed Insights 也提供了API供我們使用。同樣的,我們也可以把它集成到 CI 中。

    4.3 CrUX

    Chrome UX Report (CrUX)是指匯聚了成千上萬條用戶體驗數(shù)據(jù)的數(shù)據(jù)報告集,它是經(jīng)過用戶同意才進(jìn)行上報的,目前存儲在 Google BigQuery 上,可以使用賬號登陸進(jìn)行查詢。它測量了所有的 Core Web Vitals 指標(biāo)。

    上面提到的 PageSpeed Insights 工具就是結(jié)合 CrUX 的數(shù)據(jù)進(jìn)行分析給出的結(jié)論。

    當(dāng)然 CrUX 現(xiàn)在也提供了 API 共我們進(jìn)行查詢,可以查詢的數(shù)據(jù)包括:

    • Largest Contentful Paint

    • Cumulative Layout Shift

    • First Input Delay

    • First Contentful Paint

    原理如下:

    CrUX

    通過 API 的查詢的數(shù)據(jù)每日都更新,并匯集了過去 28 天的數(shù)據(jù)。

    具體的使用方式可以參考官方給出的demo。

    4.4 Chrome DevTools Performance 面板

    Performance 是我們最常用的本地性能分析工具。

    devtools-panel

    這里像提幾點可以關(guān)注下的功能:Frame、Timings、Main、Layers、FPS。下面一一講解。

    4.4.1 Frame

    點擊 Frame 展開后,會看到有一個一個紅色或綠色小塊,這些代表著每幀的消耗時間。目前大多數(shù)設(shè)備的屏幕刷新率為 60 次/秒,瀏覽器渲染頁面的每一幀的速率如果與設(shè)備屏幕的刷新率保持一致,即 60fps 時,我們是不會感知到頁面卡的情況的。

    我們把鼠標(biāo)移上去看看:

    frame-58

    這種是體驗順暢的情況。

    再比如:

    frame-32

    提示這一幀耗時了 30.9ms,當(dāng)前是 32fps 并且是掉幀狀態(tài)。

    4.4.2 Timings

    這里可以看到幾個關(guān)鍵指標(biāo)的時間點。

    FP:First Paint;

    FCP:First Contentful Paint;

    LCP:Largest Contenful Paint;

    DCL:DOMContentLoaded Event

    L:OnLoad Event。

    timings
    4.4.3 Main

    Main 是 DevTools 中最常用也是最重要的功能。

    main

    通過 record,我們可以查看頁面上所有操作在主線程中的執(zhí)行過程。也就是我們常說的流程:

    main-thread

    一旦有任何一個流程時間過長或頻繁發(fā)生,比如 Update Layer Tree 時間過長、頻繁出現(xiàn) RecalcStyles、Layout(重繪回流),那么需要引起注意。后面會舉一個例子。

    4.4.4 Layers

    Layers 是瀏覽器在繪制過程中生成的一個層。因為瀏覽器底層渲染的本質(zhì)是縱向分層、橫向分塊。這一塊的知識點是發(fā)生在 Renderer Process 進(jìn)程中。后面會以一個例子展開講。

    這里想提 Layers 的原因是,Layer 的渲染也會影響性能問題,而且有時候還不容易被發(fā)現(xiàn)!

    Layers 面板一般不會默認(rèn)展示出來,點擊更多->more tools->Layers 即可打開。

    點擊 Layers 面板,點擊左邊下三角展開按鈕,可以看見頁面最終生成的合成層。右邊左上角可以選擇不同緯度進(jìn)行查看。

    layers-detail

    選中某個層,可以查看該層生成的原因。

    layer-reason

    Chrome 的 Blink 內(nèi)核給出了 54 種會生成合成層的原因:

    constexpr?CompositingReasonStringMap?kCompositingReasonsStringMap[]?=?{{CompositingReason::k3DTransform,?"transform3D",?"Has?a?3d?transform"},{CompositingReason::kTrivial3DTransform,?"trivialTransform3D","Has?a?trivial?3d?transform"},{CompositingReason::kVideo,?"video",?"Is?an?accelerated?video"},{CompositingReason::kCanvas,?"canvas","Is?an?accelerated?canvas,?or?is?a?display?list?backed?canvas?that?was?""promoted?to?a?layer?based?on?a?performance?heuristic."},{CompositingReason::kPlugin,?"plugin",?"Is?an?accelerated?plugin"},{CompositingReason::kIFrame,?"iFrame",?"Is?an?accelerated?iFrame"},{CompositingReason::kSVGRoot,?"SVGRoot",?"Is?an?accelerated?SVG?root"},{CompositingReason::kBackfaceVisibilityHidden,?"backfaceVisibilityHidden","Has?backface-visibility:?hidden"},{CompositingReason::kActiveTransformAnimation,?"activeTransformAnimation","Has?an?active?accelerated?transform?animation?or?transition"},{CompositingReason::kActiveOpacityAnimation,?"activeOpacityAnimation","Has?an?active?accelerated?opacity?animation?or?transition"},{CompositingReason::kActiveFilterAnimation,?"activeFilterAnimation","Has?an?active?accelerated?filter?animation?or?transition"},{CompositingReason::kActiveBackdropFilterAnimation,"activeBackdropFilterAnimation","Has?an?active?accelerated?backdrop?filter?animation?or?transition"},{CompositingReason::kXrOverlay,?"xrOverlay","Is?DOM?overlay?for?WebXR?immersive-ar?mode"},{CompositingReason::kScrollDependentPosition,?"scrollDependentPosition","Is?fixed?or?sticky?position"},{CompositingReason::kOverflowScrolling,?"overflowScrolling","Is?a?scrollable?overflow?element"},{CompositingReason::kOverflowScrollingParent,?"overflowScrollingParent","Scroll?parent?is?not?an?ancestor"},{CompositingReason::kOutOfFlowClipping,?"outOfFlowClipping","Has?clipping?ancestor"},{CompositingReason::kVideoOverlay,?"videoOverlay","Is?overlay?controls?for?video"},{CompositingReason::kWillChangeTransform,?"willChangeTransform","Has?a?will-change:?transform?compositing?hint"},{CompositingReason::kWillChangeOpacity,?"willChangeOpacity","Has?a?will-change:?opacity?compositing?hint"},{CompositingReason::kWillChangeFilter,?"willChangeFilter","Has?a?will-change:?filter?compositing?hint"},{CompositingReason::kWillChangeBackdropFilter,?"willChangeBackdropFilter","Has?a?will-change:?backdrop-filter?compositing?hint"},{CompositingReason::kWillChangeOther,?"willChangeOther","Has?a?will-change?compositing?hint?other?than?transform?and?opacity"},{CompositingReason::kBackdropFilter,?"backdropFilter","Has?a?backdrop?filter"},{CompositingReason::kBackdropFilterMask,?"backdropFilterMask","Is?a?mask?for?backdrop?filter"},{CompositingReason::kRootScroller,?"rootScroller","Is?the?document.rootScroller"},{CompositingReason::kAssumedOverlap,?"assumedOverlap","Might?overlap?other?composited?content"},{CompositingReason::kOverlap,?"overlap","Overlaps?other?composited?content"},{CompositingReason::kNegativeZIndexChildren,?"negativeZIndexChildren","Parent?with?composited?negative?z-index?content"},{CompositingReason::kSquashingDisallowed,?"squashingDisallowed","Layer?was?separately?composited?because?it?could?not?be?squashed."},{CompositingReason::kOpacityWithCompositedDescendants,"opacityWithCompositedDescendants","Has?opacity?that?needs?to?be?applied?by?compositor?because?of?composited?""descendants"},{CompositingReason::kMaskWithCompositedDescendants,"maskWithCompositedDescendants","Has?a?mask?that?needs?to?be?known?by?compositor?because?of?composited?""descendants"},{CompositingReason::kReflectionWithCompositedDescendants,"reflectionWithCompositedDescendants","Has?a?reflection?that?needs?to?be?known?by?compositor?because?of?""composited?descendants"},{CompositingReason::kFilterWithCompositedDescendants,"filterWithCompositedDescendants","Has?a?filter?effect?that?needs?to?be?known?by?compositor?because?of?""composited?descendants"},{CompositingReason::kBlendingWithCompositedDescendants,"blendingWithCompositedDescendants","Has?a?blending?effect?that?needs?to?be?known?by?compositor?because?of?""composited?descendants"},{CompositingReason::kPerspectiveWith3DDescendants,"perspectiveWith3DDescendants","Has?a?perspective?transform?that?needs?to?be?known?by?compositor?because?""of?3d?descendants"},{CompositingReason::kPreserve3DWith3DDescendants,"preserve3DWith3DDescendants","Has?a?preserves-3d?property?that?needs?to?be?known?by?compositor?because?""of?3d?descendants"},{CompositingReason::kIsolateCompositedDescendants,"isolateCompositedDescendants","Should?isolate?descendants?to?apply?a?blend?effect"},{CompositingReason::kFullscreenVideoWithCompositedDescendants,"fullscreenVideoWithCompositedDescendants","Is?a?fullscreen?video?element?with?composited?descendants"},{CompositingReason::kRoot,?"root",?"Is?the?root?layer"},{CompositingReason::kLayerForHorizontalScrollbar,"layerForHorizontalScrollbar","Secondary?layer,?the?horizontal?scrollbar?layer"},{CompositingReason::kLayerForVerticalScrollbar,?"layerForVerticalScrollbar","Secondary?layer,?the?vertical?scrollbar?layer"},{CompositingReason::kLayerForScrollCorner,?"layerForScrollCorner","Secondary?layer,?the?scroll?corner?layer"},{CompositingReason::kLayerForScrollingContents,?"layerForScrollingContents","Secondary?layer,?to?house?contents?that?can?be?scrolled"},{CompositingReason::kLayerForSquashingContents,?"layerForSquashingContents","Secondary?layer,?home?for?a?group?of?squashable?content"},{CompositingReason::kLayerForForeground,?"layerForForeground","Secondary?layer,?to?contain?any?normal?flow?and?positive?z-index?""contents?on?top?of?a?negative?z-index?layer"},{CompositingReason::kLayerForMask,?"layerForMask","Secondary?layer,?to?contain?the?mask?contents"},{CompositingReason::kLayerForDecoration,?"layerForDecoration","Layer?painted?on?top?of?other?layers?as?decoration"},{CompositingReason::kLayerForOther,?"layerForOther","Layer?for?link?highlight,?frame?overlay,?etc."},{CompositingReason::kBackfaceInvisibility3DAncestor,"BackfaceInvisibility3DAncestor","Ancestor?in?same?3D?rendering?context?has?a?hidden?backface"}, };
    4.4.5 Rendering

    Rendering 面板也隱藏了很多好用的功能。

    4.4.5.1 Paint flashing

    勾選了 Paint flashing 后,我們就會看到頁面上有哪些內(nèi)容被重繪了:

    paint-flashing
    4.4.5.2 Layout Shift6 Regions

    勾選了 Layout Shift Regions 后,進(jìn)行交互,就可以看到哪些元素進(jìn)行了布局移動:

    layout-shift-regions
    4.4.5.3 Frame Rendering Stats

    這個工具有一個小插曲。

    Frame Rendering Stats 的前身是 FPS meter,在 Google 版本85.0.4181.0改成了 Frame Rendering Stats,但是迫于用戶抱怨,在 90 版本的時候又改回來了。

    Frame Rendering Stats 主要顯示不掉幀率。而 FPS 側(cè)重于顯示每秒的刷新率 fps。

    Chrome 為什么要改成不掉幀率,是因為認(rèn)為不掉幀率更能反映頁面的順暢度。而 FPS 顯示每一秒渲染的幀數(shù)雖然能一定程度反映頁面順暢度,但是在一些特殊情況例如沒有激活或空閑的頁面,fps 會比較低,這樣并不能反映真實情況。

    frame-vs-fps

    (圖片引自blink dev 論壇討論)

    4.4.6 Memory

    在大型項目中,內(nèi)存問題也是有發(fā)生。DevTools 也提供了內(nèi)存分析工具供我們使用。

    點擊 Memory 面板,點擊錄制按鈕。

    memory

    點擊錄制后,會看到當(dāng)前狀態(tài)下內(nèi)存的占用情況,根據(jù)大小排序,我們可以定位到內(nèi)存占用過多的地方。

    memory-snapshot

    4.5 Search Console

    Google Search Console 其實就是監(jiān)控和維護(hù)網(wǎng)站在 Google 搜索結(jié)果中的展示情況以及排查問題的平臺。數(shù)據(jù)來源是 CrUX。

    它會展示 3 個 Core Web Vitals metrics: LCP, FID, CLS。如果發(fā)現(xiàn)有問題,可以配合 PageSpeed 一起使用,分析問題。

    search-console

    (圖片引自 vital-tools)

    4.6 web.dev

    web.dev/measure是 google 官方提供的測量性能工具,也會提供類似 PageSpeed Insight 的指標(biāo),還會提供一些具體代碼更改建議。

    web-dev-1web-dev-2

    4.7 Web Vitals extension

    Google 也提供了擴(kuò)展工具去測量 Core Web Vitals。可以從Store中進(jìn)行安裝。

    web-vitals-extension

    4.8 工具:思考與總結(jié)

    當(dāng)我們了解了這么多工具之后,琳瑯滿目,我們該如何選擇?如何使用好這些工具進(jìn)行分析?

    • 首先我們可以使用 Lighthouse,在本地進(jìn)行測量,根據(jù)報告給出的一些建議進(jìn)行優(yōu)化;

    • 發(fā)布之后,我們可以使用 PageSpeed Insights 去看下線上的性能情況;

    • 接著,我們可以使用 Chrome User Experience Report API 去撈取線上過去 28 天的數(shù)據(jù);

    • 發(fā)現(xiàn)數(shù)據(jù)有異常,我們可以使用 DevTools 工具進(jìn)行具體代碼定位分析;

    • 使用 Search Console's Core Web Vitals report 查看網(wǎng)站功能整體情況;

    • 使用 Web Vitals 擴(kuò)展方便的看頁面核心指標(biāo)情況;

    5. 談?wù)劚O(jiān)控

    最后一個章節(jié)想來談?wù)劚O(jiān)控。

    我們在做性能優(yōu)化的時候,常常會通過各種線上打點,來收集用戶數(shù)據(jù),進(jìn)行性能分析。沒錯,這是一種監(jiān)控手段,更精確的說,這是一種"事后"監(jiān)控手段。

    "事后"監(jiān)控固然重要,但我們也應(yīng)該考慮"事前"監(jiān)控,否則,每次發(fā)布一個需求后,去線上看數(shù)據(jù)。咦,發(fā)現(xiàn)數(shù)據(jù)下降了,然后我們?nèi)ゲ榇a,去查數(shù)據(jù),去查原因。這樣性能優(yōu)化的同學(xué)永遠(yuǎn)處于"追趕者"的角色,永遠(yuǎn)跟在屁股后面查問題。

    舉個例子,我們可以這樣去做"事前"監(jiān)控。

    建立流水線機(jī)制。流水線上如何做呢?

    • Lighthouse CI 或 PageSpeed Insights API:把 Lighthouse 或 PageSpeed Insights API 集成到 CI 流水線中,輸出報告分析。

    • Puppeteer 或 Playwright:使用 E2E 自動化測試工具集成到流水線模擬用戶操作,得到 Chrome Trace Files,也就是我們平常錄制 Performance 后,點擊左上角下載的文件。Puppeteer 和 Playwright 底層都是基于Chrome DevTools Protocol。

      perf-download
    • Chrome Trace Files:根據(jù)規(guī)則分析 Trace 文件,可以得到每個函數(shù)執(zhí)行的時間。如果函數(shù)執(zhí)行時間超過了一個臨界值,可以拋出異常。如果一個函數(shù)每次的執(zhí)行時間都超過了臨界值,那么就值得注意了。但是還有一點需要思考的是:函數(shù)執(zhí)行的時間是否超過臨界值固然重要,但更重要的是這是不是用戶的輸入響應(yīng)函數(shù),與用戶體驗是否有關(guān)。

      圖片來自Flo Sloot的Jank: You can measure what your users can feel.
    • 輸出報告。定義異常臨界值。如果異常過多,考慮是否卡發(fā)布流程。

    6. 總結(jié)

    我們來回顧一下前面的內(nèi)容:

    第一部分,講了瀏覽器整體架構(gòu)和渲染相關(guān)進(jìn)程.為什么把這個章節(jié)也放到這篇性能優(yōu)化的文章中?瀏覽器對于我們前端開發(fā)來說,是一個 sandbox 或者 darkbox。我們知道 js、html、css 結(jié)合起來就能實現(xiàn)我們的需求,但如果知道它是如何去渲染、執(zhí)行、處理我們的代碼,不管是對做需求還是性能優(yōu)化,都能更知其然和所以然。

    第二部分,雅虎軍規(guī)是多年前提出的非常經(jīng)典的優(yōu)化建議。至今對于我們異常有很強(qiáng)的指導(dǎo)作用。你會發(fā)現(xiàn)它是從頁面加載、頁面渲染、到頁面交互全面的一個指導(dǎo)建議。與今天 Chrome 和 W3c 提出的 Web Vitals 思路依然類似。

    第三部分,性能指標(biāo)。參考標(biāo)準(zhǔn)與業(yè)內(nèi)標(biāo)桿的建議,能更好地指導(dǎo)我們進(jìn)行優(yōu)化。

    第四部分,性能工具。工欲善其事,必先利其器。這個道理大家都懂,運用好工具,才能讓我們更加事半功倍。

    第五部分,監(jiān)控在性能優(yōu)化中占很重要的部分,"事前"監(jiān)控更重要,防患于未然。讓性能優(yōu)化成為一個預(yù)防者而不是追趕者。

    羅里吧嗦說了很多,當(dāng)然還有很多性能優(yōu)化的細(xì)節(jié)沒有講到,如果有錯誤的地方歡迎指正?;蛘哂惺裁春梅椒ê媒ㄗh也強(qiáng)烈歡迎私聊交流一下。

    沒有困難的工作

    參考文章:

    • https://web.dev/learn-web-vitals/

    • https://developers.google.com/web/updates/2018/09/inside-browser-part1

    • https://aerotwist.com/blog/the-anatomy-of-a-frame/

    • https://www.chromium.org/developers/how-tos/getting-around-the-chrome-source-code

    • https://medium.com/punching-performance/jank-you-can-measure-what-your-users-can-feel-e5713df2845f

    視頻號最新視頻

    總結(jié)

    以上是生活随笔為你收集整理的浏览器性能优化实战的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。