頁面CLS 最佳化實踐

碼農談IT發表於2023-11-03

來源:之家技術


1. CLS 誕生背景

在深入探討最佳化實踐之前,讓我們先了解一下 CLS(累計佈局偏移)及其最佳化的重要性。我們在瀏覽網頁時可能會遇到這樣的情況:當我們正聚精會神地閱讀網頁內容時,突然發現內容在沒有任何預警的情況下被擠到了另一個位置。尤其是當您已經閱讀了一小段文字後,不得不費勁尋找原來的閱讀位置,這種意想不到的偏移可能會讓您感到極度不悅。更糟糕的是,當您準備點選一個連結或按鈕時,正巧在手指按下的瞬間,連結突然移位,導致您誤點到其他內容。通常情況下,這樣的網站會給使用者帶來極差的體驗,甚至瞬間的位移錯誤互動可能造成一定程度的破壞。

通常,頁面元素髮生意外偏移的原因包括非同步資源載入以及在 DOM 元素上方動態插入新的元素等。具體而言,這些偏移可能源於圖片或影片元素尺寸未設定、不受控的第三方廣告引入或者小元件自身大小的動態調整。
因此,我們需要一個指標來度量意外偏移對使用者“視覺穩定性”產生的影響。


2. CLS 定義

        CLS (Cumulative Layout Shift) 累計佈局偏移指標,透過度量“視覺穩定性”反映使用者對頁面佈局偏移所產生的主觀視覺體驗。

        Google 定義每隔 5 秒作為一個 CLS 監聽"時間視窗",並且要求在每個"時間視窗"內,相鄰兩次偏移的時間間隔小於 1 秒。在單個"時間視窗"內,CLS 值是由多個元素的偏移值累加得到的。如果頁面中的元素佈局持續發生偏移(持續時間大於 5 秒),則可能會形成多個"時間視窗",每個"時間視窗"的 CLS 值可能不同。在這種情況下,將最大"時間視窗"的 CLS 值作為該頁面的最終 CLS 值。

如下圖:CLS = Max(session window 1,session window 2,session window 3) = 0.105

頁面CLS 最佳化實踐


2.1

如何對 CLS 評級?

谷歌定義頁面 CLS 數值應該控制在 0.1 以內,包括移動和桌面裝置。為了確保大多數使用者達成目標,一個良好的測量閾值為第 75 個百分位數。

頁面CLS 最佳化實踐

3. CLS 如何計算

3.1

不穩定性元素

任何位於可視區域內的可見元素,如果其起始位置在兩幀之間發生變化,則被視為“不穩定元素”。這些“不穩定元素”用於計算佈局偏移。需要注意的是,如果新元素新增到 DOM 或現有元素的尺寸發生改變,只要這些變化不導致其他可見元素的起始位置發生變化,它們就不會被計算為佈局偏移。

3.2

計算公式

佈局偏移分數 = 影響分數(impact fraction) * 距離分數 (distance fraction)

舉例 :  0.07    =  0.5 * 0.14

► 影響分數

        該計算因子的含義是測量“不穩定元素”對兩幀之間的可視區域產生的影響,即可視元素的起始位置在兩幀之間發生變化後,兩幀中元素可視區域的合集佔總可視區域的百分比。

    如下圖所示:

頁面CLS 最佳化實踐

        上圖中黃色塊 P 標籤為“不穩定元素”,其自身元素佔可視區域的 50%,在兩幀中移動了可視區域高度的 25%,紅色虛線表示兩幀中元素的可見區域集合,該集合佔總可視區域的 75%,因此在本例中影響分數為 0.75。


► 距離分數

        該計算因子的含義是,測量不穩定元素相對於可視區域在一幀中位移的最大距離(該位移距離可以是水平或者垂直方向,同時存在時取最大者),除以可視區域的最大尺寸維度(尺寸維度可以是寬度或高度,取較大者)。如下圖所示:

頁面CLS 最佳化實踐

        上圖中最大可視區域尺寸維度是高度,不穩定元素位移的距離為可視區域高度的 25%,因此距離分數為 0.25 。

        根據公式:佈局偏移分數 = 影響分數 * 距離分數,以上圖為例 CLS 值為 0.75(影響分數)* 0.25(距離分數)= 0.1875 。

3.3

舉個例子

頁面CLS 最佳化實踐

        如上圖所示,點選 “Click Me!” 按鈕時綠色框部分為不穩定元素,發生了向下的位移,其影響範圍為紅色虛線框部分(底部超出可視區域不做計算),佔總可視區域的 50%,即影響分數為 0.5 。

        距離分數由紫色箭頭表示,在高度尺寸維度發生了位移,其位移距離為可視區域高度的 14%,即距離分數為 0.14 。

        所以佈局偏移分數是 0.5 * 0.14 = 0.07 。

3.4

符合預期的佈局偏移

佈局偏移並不總是壞事。(佈局偏移只有在使用者並不期望其發生時才算是壞事), 以下兩種情況, 不會影響 CLS 分數。

(1)由使用者發起的佈局偏移

       使用者互動(如單擊連結、點選按鈕、在搜尋框中鍵入資訊等), 500毫秒之後發生的 CLS 都會帶有"hadRecentInput"標記, 不會影響 CLS 分數 。  

注意 : "hadRecentInput" 僅適用於不連續輸入事件 (如 tap, click, or keypress), 而連續輸入事件不會被標記(如 scrolls, drags, or pinch and zoom gestures)

        (2)動畫和過渡

        動畫和過渡如果做得好,確實是一個在更新頁面內容時不讓使用者感到突兀的好方法。CSS transform 屬性可以幫助我們在不觸釋出局偏移的情況下為元素設定動畫:

        用 transform: scale() 來替代和調整 height 和 width 屬性。

        如需使元素能夠四處移動,可以用 transform: translate() 來替代和調整 top、right、bottom 或 left 屬性。


4. CLS 測量

        在上部分中對 CLS 的基礎理論進行了介紹,並透過相關示例演示了 CLS 值是如何計算的,接下來我們看一下在瀏覽器中如何透過工具對 CLS 進行跟蹤測量。

4.1

DevTools

        開啟 Chrome DevTools,在 Performance 標籤選項卡中點選“錄製”按鈕並重新整理頁面,您將得到 CLS 的跟蹤資訊,大部分情況下您可以透過該功能還原定位線上 CLS 問題。  

        如下圖:

頁面CLS 最佳化實踐

        點選不同的“紅色塊”可以檢視其對應的 CLS 值及 DOM元素和相關偏移資訊。如下圖:

頁面CLS 最佳化實踐

4.2

透過 JavaScript 測量 CLS

      我們可以使用 JavaScript 和瀏覽器原生的 PerformanceObserver 來測量 CLS,透過監聽 layout-shift 條目,並根據每 5 秒為一個視窗期的最大分數累計規則,對這些監聽到的 layout-shift 條目資料進行計算。

          實現程式碼如下:



































let clsValue = 0;let clsEntries = [];let sessionValue = 0;let sessionEntries = [];
new PerformanceObserver((entryList) => {  for (const entry of entryList.getEntries()) {    // 只將不帶有最近使用者輸入標誌的佈局偏移計算在內。    if (!entry.hadRecentInput) {      const firstSessionEntry = sessionEntries[0];      const lastSessionEntry = sessionEntries[sessionEntries.length - 1];      // 如果條目與上一條目的相隔時間小於 1 秒且      // 與會話中第一個條目的相隔時間小於 5 秒,那麼將條目      // 包含在當前會話中。否則,開始一個新會話。      if (sessionValue &&          entry.startTime - lastSessionEntry.startTime < 1000 &&          entry.startTime - firstSessionEntry.startTime < 5000) {        sessionValue += entry.value;        sessionEntries.push(entry);      } else {        sessionValue = entry.value;        sessionEntries = [entry];      }      // 如果當前會話值大於當前 CLS 值,      // 那麼更新 CLS 及其相關條目。      if (sessionValue > clsValue) {        clsValue = sessionValue;        clsEntries = sessionEntries;        // 將更新值(及其條目)記錄在控制檯中。        console.log('CLS:', clsValue, clsEntries)      }    }  }}).observe({type: 'layout-shift', buffered: true});

4.3

web-vitals Javascript 庫

        GoogleChrome 提供了 web-vitals JavaScript 開源庫,用於測量使用者瀏覽器端真實效能,除 CLS 指標外還包括 FID(First Input Delay)、TTFB(Time to First Byte)、FCP(First Contentful Paint)、LCP(Largest Contentful Paint)、INP(Interaction to next Paint)。程式碼示例:






import { onCLS } from 'web-vitals';
onCLS((metric) => {  console.log(metric);});

5. CLS 採集與上報

        透過使用 web-vitals JavaScript 庫,我們可以便捷地獲取頁面的 CLS 和其他效能指標,這在很大程度上簡化了資料測量工作。接下來,為了收集使用者頁面的真實 CLS 效能資料,我們可以設計和開發一套資料採集 sdk,將採集到的資料上報至伺服器端進行儲存。隨後,透過後端大資料分析展示頁面的整體 CLS 效能水平,從而為開發人員提供有針對性的最佳化建議。

5.1

採集、上報時機

        在資料採集和上報過程中,我們應確保資料的準確性和完整性,同時也要充分考慮對採集上報時機的設定,這樣可以確保對整體資料統計評分的公平性。

        CLS 的"時間視窗"為 5 秒,因此在 sdk 初始化後的第 5 秒,我們將頁面效能資料進行資料採集與上報。然而,sdk 作為一個獨立的 JS 檔案,可以在頁面中透過同步或非同步載入。特別是在非同步載入方式下,如果延遲 5 秒後再進行採集上報,很可能已超過第一個 CLS "時間視窗"。這樣的資料採集可能會影響最終統計評分的公平性,其影響主要體現在以下兩個方面:

► 大於 5 秒上報

        受使用者停留頁面的時間因素影響,時間越靠後使用者資料丟失的機率就越大。

頁面CLS 最佳化實踐

         如上圖所示,採集工具 js(紅框內) 下載時機在第 3.3 秒開始下載,如果在此基礎上再延遲 5 秒後採集上報資料,真實的採集時間為 8.3 秒,假如使用者停留時長小於 8.3 秒,此情況下若不採取措施該取樣資料將會遺漏掉。

► 小於 5 秒上報

        相反,如果將延時時間設定為小於 5 秒(如參考 Sentry@7.64.0 的預設設定時間 1 秒),所得的測量值準確性將得不到保證。為此我們用 web-vitals 做了一次資料分析對比實驗,在日均 pv 約 10 萬左右的網站上持續 3 天對 CLS 資料進行測量,並分別設定在 5 秒和 1 秒後採集和上報。對比結果發現 1 秒上報其各項指標資料丟失率非常大,且資料值偏小,無法保證其準確性。

        如下圖所示:

頁面CLS 最佳化實踐

► 最優方案

        為了解決 sdk 初始化時間大於或小於第一個 CLS "時間視窗"所產生的影響,我們透過研究 web-vitals 原始碼發現 CLS 以及其他效能指標以 PerformanceNavigationTiming 的 "startTime" 為相對起點。因此,可以將 sdk 初始化時間與該 "startTime" 相減。如果兩者的差值大於 5 秒,則可以立即進行資料採集與上報;反之,可以將距離 5 秒的差值作為延遲等待時間。

     PerformanceNavigationTiming 模型如下圖所示:

頁面CLS 最佳化實踐

► 頁面關閉時採集上報

        當使用者停留時長不足 5 秒提前關閉頁面時,可能導致資料採集丟失。為了解決這個問題,我們可以透過監聽 visibilitychange、pageHide 事件來採集頁面關閉前的 CLS 資料。這種方案儘可能地確保資料不丟失,但無法保證第一個"時間視窗"的 5 秒準確度。

      因此,該方案所採集上報的資料只是儘可能的反映頁面真實取樣 PV,並不參與最終的統計評分。

5.2

採集流程設計

        因為 CLS 值的獲取是一個持續進行的過程,在監聽 DOM 佈局偏移過程中,資料物件透過非同步回撥方式傳遞。這個持續動作可能會貫穿整個頁面生命週期。由於獲取到的資料需要上報到伺服器端,不斷的進行資料上報將對後端服務帶來巨大壓力。因此,需要設計一個合理的採集流程以避免上述問題。

        採集流程如下圖所示:

頁面CLS 最佳化實踐

   1) sdk 初始化時已滿足 5 秒,則立即執行 CLS 資料採集並上報,反之延遲至 5 秒再完成採集上報。

  2)使用者停留時長不足 5 秒時頁面關閉,監聽 visibilitychange、pageHide 事件完成採集,並透過 sendBeacon 方式上報。

   3) 整個頁面生命週期中只允許一次採集、上報。

5.3

資料上報

        在上面流程設計圖中我們設計了兩種上報方式,sdk 初始化後 5 秒上報採用 XmlHttpRequest,頁面隱藏或關閉採用 navigator.sendBeacon。

        兩種方式詳細對比見下圖:

頁面CLS 最佳化實踐

5.4

CLS 元素輔助定位

        在發現網站頁面的 CLS 值較高時,我們需要使用開發的 sdk 協助開發人員將 CLS 值與其 DOM 元素關聯起來,以便確定頁面中累計偏移較大的元素。Web-vitals v3.0.0 及更高版本中新增了 attribution 除錯功能,能夠將 DOM 元素與 CLS 值關聯並輸出。然而,這導致了包體積增加了 7K,並且輸出內容格式固定,無法實現定製化。

        為了定位導致 CLS 值過大的元素,同時防止 js 庫體積變大,我們對 web-vitals 中 onCLS 返回的 layout-shift 條目物件進行自定義解析,並對 DOM 元素的 “domPath”路徑輸出,既幫助開發人員快速定位、修復問題,又縮小了 js 庫體積。

        如圖:

頁面CLS 最佳化實踐

6. 最佳化實踐

        利用 sdk 我們已收集取到了頁面真實資料,下面透過一個真實案例分享我們是如何進行 CLS 最佳化的。

6.1

頁面結構介紹

        為了便於大家對頁面結構有清晰的瞭解,我們在最佳化之前對頁面結構按功能做了如下劃分。

1)  文章內容區(Vue 渲染)。

2)  廣告部分(之家廣告 js 載入)。

3)  頁頭頁尾(公共 js 載入)。

4)  其它(各類第三方 js 庫,如統計、埋點、前端各類元件等)。

如圖所示:

頁面CLS 最佳化實踐

6.2

頁面邏輯說明

        本專案採用 SSR + Vue 實現前端頁面渲染,在功能職責上 SSR 僅提供一個空頁面模版,其主要內容的渲染工作由 Vue 承擔,廣告位的渲染部分被包含其中,廣告位渲染鏈路為:

第一步,渲染文章內容部分,由 Vue 繫結資料非同步實現。

第二步,判斷有無廣告位,先由 ajax 非同步請求 isHave 介面,判斷是否存在廣告位,如存在則動態下載廣告位 js 及相關資源(圖片、樣式檔案)。

第三步,渲染廣告位, 廣告 js 及資原始檔下載完成後,動態插入 DOM 並完成渲染。

6.3

頁面問題說明

        Vue 渲染在廣告位 js 及相關資源的載入之後,且廣告位資源的下載需要等待 isHave 非同步請求返回後,導致廣告內容的渲染過程鏈路被拉長。其次廣告位內容尺寸未知,因此頁面在渲染過程中頁面會因廣告位的動態載入渲染出現一次“抖動”現象,最終導致頁面的 CLS 值過大。

下圖動畫演示了廣告出現的整個渲染鏈路:

頁面CLS 最佳化實踐


6.4

最佳化措施及內容

在開始最佳化之前我們需要理順各個資源的功能及依賴關係,並將資源根據功能做了如下劃分:
► 本部門公用資源
公共 js 庫。
統計程式碼庫。
► 其它資源庫及頁面基礎靜態資源
頁頭、頁尾資原始檔。
廣告庫。
頁面靜態資源(css、js)。
最佳化思路為:讓廣告位提前並獨立載入渲染,在 SSR 服務端渲染時保留廣告位, 並在 SSR 渲染完文章部分後,第一時間由客戶端發起請求,獲取廣告相關資訊。
相關最佳化措施如下:
1)  漸進式載入,優先保證首屏廣告位提前渲染,渲染順序為:文章內容(SSR)-> 廣告(DOM傳送門)-> Vue 應用 -> 頁頭頁尾。
2)  廣告渲染從 Vue 應用中剝離,並在 SSR 渲染時動態預留廣告位 “DOM 傳送門” 用以穩定 CLS,Vue 與廣告位兩者獨立渲染,兩者再無關聯。
3)  廣告位“DOM傳送門”區域預設固定尺寸,當 dom 解析到某個廣告坑位時,透過 js 將之前提前渲染好的廣告移動到當前廣告坑位。
最佳化前後效果對比,如下圖:
頁面CLS 最佳化實踐

6.5

資料評分效果


頁面CLS 最佳化實踐


7. 總結

透過本文我們對 CLS 進行了詳細的闡述,並介紹瞭如何對 CLS 數值進行追蹤獲取,最後透過真實案例分享如何對 CLS 進行最佳化來提升使用者體驗。CLS的最佳化對網站效能和使用者體驗的提升是很重要的,除此之外它還包括以下幾個方面的影響提升:
►  搜尋引擎最佳化
Google已經將 CLS 作為其評估網站效能的核心指標之一,因此最佳化CLS值對於提高搜尋引擎排名非常重要,該指標的提升會吸引更多的流量。
► 轉化率
頁面元素的穩定性直接影響到使用者在網站上採取行動的可能性。如果頁面佈局不穩定,使用者可能會在嘗試填寫表單、點選按鈕或完成購買等操作時遇到困難。因此,一個穩定的頁面有助於使用者完成目標操作,從而提升相關業務轉化率。
總之,CLS指標是衡量網站效能和使用者體驗的重要標準,最佳化CLS值可以提升使用者體驗、搜尋引擎排名、轉化率等,為網站帶來長期收益。


[參考引文] 

https://web.dev/optimize-cls/

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2992688/,如需轉載,請註明出處,否則將追究法律責任。

相關文章