運用用戶端提示根據使用者進行調整

開發速度快且無論何時何地都能順暢運作的網站,可能會是個棘手的挑戰。裝置功能種類繁多,連線的網路品質也各有不同,因此這項任務可能難以完成。雖然我們可以利用瀏覽器功能改善載入效能,但我們如何得知使用者的裝置功能或網路連線品質?解決方法就是客戶提示

用戶端提示是一組選擇加入的 HTTP 要求標頭,可讓我們深入瞭解使用者裝置和連線網路的這些層面。透過使用伺服器端的這項資訊,我們可以根據裝置和/或網路狀況變更內容傳遞方式。這有助於我們打造更包容的使用者體驗。

內容協商

用戶端提示是另一種內容協商方法,也就是根據瀏覽器要求標頭變更內容回應。

內容協商的一個例子涉及 Accept 要求標頭。它會說明瀏覽器可理解的內容類型,供伺服器用來協商回應。針對圖片要求,Chrome 的 Accept 標頭內容如下:

Accept: image/webp,image/apng,image/*,*/*;q=0.8

雖然所有瀏覽器都支援 JPEG、PNG 和 GIF 等圖片格式,但在這種情況下,Accept 會指出瀏覽器「也」支援 WebPAPNG。我們會根據這些資訊,為每個瀏覽器協商最合適的圖片類型:

<?php
// Check Accept for an "image/webp" substring.
$webp = stristr($_SERVER["HTTP_ACCEPT"], "image/webp") !== false ? true : false;

// Set the image URL based on the browser's WebP support status.
$imageFile = $webp ? "whats-up.webp" : "whats-up.jpg";
?>
<img src="<?php echo($imageFile); ?>" alt="I'm an image!">

Accept 一樣,用戶端提示也是協商內容的另一種方式,但在裝置功能和網路條件的情境下。有了用戶端提示,我們就能根據使用者的個別體驗做出伺服器端效能決策,例如決定是否應為網路連線不佳的使用者提供非必要資源。本指南將說明所有可用的提示,以及您可以利用這些提示讓內容更符合使用者需求的幾種方法。

啟用

Accept 標頭不同,用戶端提示不會自動顯示 (Save-Data 除外,我們稍後會討論這個問題)。為了盡量減少要求標頭,您必須在使用者要求資源時傳送 Accept-CH 標頭,選擇要接收哪些用戶端提示:

Accept-CH: Viewport-Width, Downlink

Accept-CH 的值是逗號分隔的清單,其中包含網站會用來判斷後續資源要求結果的提示。當用戶端讀取這個標頭時,會收到「這個網站需要 Viewport-WidthDownlink 用戶端提示」的訊息。請勿擔心特定提示本身。我們稍後會討論這些問題。

您可以使用任何後端語言設定這些選擇加入標頭。例如,可以使用 PHP 的 header 函式。您甚至可以使用 <meta> 標記上的 http-equiv 屬性設定這些選擇加入標頭:

<meta http-equiv="Accept-CH" content="Viewport-Width, Downlink" />

所有用戶端提示!

用戶端提示可描述以下一項或兩項資訊:使用者使用的裝置,以及他們用來存取網站的網路。接下來,我們將簡要介紹所有可用的提示。

裝置提示

部分用戶端提示會描述使用者裝置的特性,通常是螢幕特性。其中有些可協助您為特定使用者的畫面選擇最佳媒體資源,但並非所有都以媒體為主。

在開始查看這份清單之前,建議您先瞭解幾個用來描述螢幕和媒體解析度的關鍵字詞:

內在大小:媒體資源的實際尺寸。舉例來說,如果您在 Photoshop 中開啟圖片,圖片大小對話方塊中顯示的尺寸會描述其內在大小

經密度修正的內在大小:經過像素密度修正後的媒體資源尺寸。這是圖片的內在大小除以裝置像素比率後的結果。舉例來說,我們來看看以下標記:

<img
  src="whats-up-1x.png"
  srcset="whats-up-2x.png 2x, whats-up-1x.png 1x"
  alt="I'm that image you wanted."
/>

假設此情況下 1x 圖片的內在大小為 320 x 240,而 2x 圖片的內在大小為 640 x 480。如果此標記是由在螢幕裝置像素比率為 2 (例如 Retina 螢幕) 的裝置上安裝的用戶端剖析,系統會要求 2x 圖片。2x 圖片的經密度修正的內在大小為 320x240,因為 640x480 除以 2 等於 320x240。

外部大小:套用 CSS 和其他版面配置因素 (例如 widthheight 屬性) 後,媒體資源的大小。假設您有一個 <img> 元素,可載入經密度修正的內在大小為 320x240 的圖片,但該元素也具有 CSS widthheight 屬性,分別套用 256px192px 的值。在這個範例中,該 <img> 元素的外部大小會變成 256x192。

內在大小與外在大小的插圖。顯示大小為 320x240 像素的方塊,並標示為「Intrinsic Size」。其中有一個大小為 256x192 像素的小方塊,代表已套用 CSS 的 HTML img 元素。這個方塊的標籤為「EXTRINSIC SIZE」。右側的方塊包含套用至元素的 CSS,該 CSS 會修改 img 元素的版面配置,使其外部大小與內部大小不同。
圖 1:內在尺寸與外在尺寸的插圖。套用版面配置因素後,圖片會獲得外部大小。在這種情況下,套用 width: 256px;height: 192px; 的 CSS 規則,可將內在大小為 320x240 的圖片轉換為外在大小為 256x192 的圖片。

瞭解了一些術語後,我們就來談談可用的裝置專屬客戶端提示清單。

Viewport-Width

Viewport-Width 是使用者可視區域的寬度,以 CSS 像素為單位:

Viewport-Width: 320

您可以搭配其他螢幕專屬提示使用這項提示,為特定螢幕大小 (例如 art direction) 提供最適合的圖片處理方式 (例如裁剪),或是省略目前螢幕寬度不需要的資源。

DPR

DPR 是裝置像素比例的簡寫,用來回報使用者螢幕的實體像素與 CSS 像素比率:

DPR: 2

在選取與螢幕像素密度相符的圖片來源時,這項提示很實用 (例如 x 描述符在 srcset 屬性中的作用)。

寬度

當使用 sizes 屬性<img><source> 標記觸發圖片資源要求時,就會顯示 Width 提示。sizes 會告知瀏覽器資源的外部大小;Width 會使用該外部大小,要求內部大小為目前版面配置最佳的圖片。

舉例來說,假設使用者要求的頁面寬度為 320 個 CSS 像素,且螢幕的 DPR 為 2。裝置會載入包含 <img> 元素的文件,其中 sizes 屬性值為 85vw (即 所有螢幕大小的可視區域寬度 85%)。如果已選擇啟用 Width 提示,用戶端會將此 Width 提示傳送至伺服器,並要求 <img>src

Width: 544

在這種情況下,用戶端會向伺服器提示,要求圖片的最佳內在寬度為檢視區寬度 (272 像素) 的 85%,再乘以螢幕的 DPR (2),等於 544 像素。

這項提示特別強大,因為它不僅考量螢幕經密度修正後的寬度,還會將這項重要資訊與版面配置中的圖片外在大小進行調和。這樣一來,伺服器就能協商出最適合螢幕版面配置的圖片回應。

Content-DPR

您可能已經知道螢幕有裝置像素比例,但資源也有各自的像素比例。在最簡單的資源選取用途中,裝置和資源之間的像素比例可以相同。不過!如果同時使用 DPRWidth 標頭,資源的外在大小可能會產生兩者不同的情況。這時 Content-DPR 提示就派上用場。

與其他用戶端提示不同,Content-DPR 並非伺服器使用的要求標頭,而是伺服器在使用 DPRWidth 提示選取資源時必須傳送的回應標頭。Content-DPR 的值應為以下等式計算結果:

Content-DPR = [所選圖片資源大小] / ([Width] / [DPR])

傳送 Content-DPR 要求標頭後,瀏覽器就會知道如何根據螢幕的裝置像素比例和版面配置,縮放指定圖片。否則圖片可能無法正確縮放。

裝置記憶體

Device-MemoryDevice Memory API 的技術層面組成部分,可揭露目前裝置的記憶體容量約值 (以 GiB 為單位):

Device-Memory: 2

這個提示的可能用途是減少在記憶體有限的裝置上,傳送至瀏覽器的 JavaScript 數量,因為 JavaScript 是瀏覽器通常載入的資源密集型內容類型。或者,您也可以傳送解析度較低的圖片,因為這類圖片需要較少的記憶體進行解碼。

網路提示

Network Information API 提供另一類用戶端提示,可說明使用者的網路連線效能。我認為這些是最好用的提示。有了這些資訊,我們就能改變在連線速度較慢的情況下,向用戶端提供資源的方式,為使用者提供量身打造的體驗。

即時文字訊息

RTT 提示會在應用程式層提供大約的往返時間 (以毫秒為單位)。RTT 提示與傳輸層 RTT 不同,包含伺服器處理時間。

RTT: 125

這項提示很實用,因為延遲時間會影響載入效能。使用 RTT 提示,我們可以根據網路回應速度做出決策,這有助於加快整個體驗的傳送速度 (例如,透過省略某些要求)。

雖然延遲時間對載入效能至關重要,但頻寬也是影響因素之一。Downlink 提示以每秒位元組數 (Mbps) 為單位,顯示使用者連線的近似下游速度:

Downlink: 2.5

Downlink 可與 RTT 搭配使用,根據網路連線品質變更內容傳送至使用者的做法。

ECT

ECT 提示代表「有效連線類型」。其值是連線類型列舉清單的其中一個,每個連線類型都會描述RTTDownlink 值的指定範圍內的連線

這個標頭不會說明實際連線類型為何,例如不會回報閘道是行動電話基地台還是 Wi-Fi 存取點。而是分析目前連線的延遲時間和頻寬,並判斷最類似的網路設定檔。舉例來說,如果您透過 Wi-Fi 連線至速度較慢的網路,ECT 可能會填入 2g 的值,這是最接近「有效」連線的近似值:

ECT: 2g

ECT 的有效值為 4g3g2gslow-2g。這個提示可用於評估連線品質,並在之後使用 RTTDownlink 提示進行微調。

Save-Data

Save-Data 並非描述網路狀況的提示,而是使用者偏好設定,表示網頁應傳送較少資料。

我傾向將 Save-Data 歸類為網路提示,因為您會用它執行的許多操作與其他網路提示類似。使用者也可能會在高延遲/低頻寬環境中啟用這項功能。當這個提示出現時,一律會如下所示:

Save-Data: on

在 Google 這裡,我們曾討論過如何使用 Save-Data。這可能會對效能造成重大影響。這表示使用者希望你減少傳送內容!如果您能傾聽並採取行動,使用者一定會很感激。

融會貫通、靈活運用

您可以根據需求使用用戶端提示。由於這些資料提供豐富的資訊,因此您有許多選項可供選擇。為了讓您有更多想法,我們以 Sconnie Timber 為例,說明客戶端提示可為這家位於美國中西部偏遠地區的虛構木材公司帶來哪些好處。如同偏遠地區常見的情況,網路連線可能不穩定。這正是用戶端提示等技術真正能為使用者帶來改變的時刻。

回應式圖片

除了最簡單的回應式圖片用途之外,其他用途都可能變得複雜。如果您為不同螢幕大小不同格式,使用同一張圖片的多種處理方式變化版本,該怎麼辦?這類標記會很快變得非常複雜。很容易出錯,也容易忘記或誤解重要概念 (例如 sizes)。

雖然 <picture>srcset 無疑是實用的工具,但在複雜用途下,開發和維護這些工具可能會耗費許多時間。我們可以自動產生標記,但這項作業也相當困難,因為 <picture>srcset 提供的功能相當複雜,因此需要以能維持其提供的彈性的方式進行自動化。

您可以使用用戶端提示簡化這項作業。使用用戶端提示協商圖片回應的情況可能如下所示:

  1. 如果適用於工作流程,請先選取圖片處理方式 (即藝術指導圖像),方法是勾選 Viewport-Width 提示。
  2. 選取圖片解析度時,請檢查 Width 提示和 DPR 提示,並選擇符合圖片版面配置大小和螢幕密度的來源 (類似於 srcset 中的 xw 描述符運作方式)。
  3. 選取瀏覽器支援的最佳檔案格式 (Accept 可協助我們在大多數瀏覽器中執行此操作)。

就我虛構的木材公司客戶而言,我使用 PHP 開發了一個採用客戶端提示的簡單回應式圖片選取例程。這表示您不必將此標記傳送給所有使用者:

<picture>
  <source
    srcset="
      company-photo-256w.webp   256w,
      company-photo-512w.webp   512w,
      company-photo-768w.webp   768w,
      company-photo-1024w.webp 1024w,
      company-photo-1280w.webp 1280w
    "
    type="image/webp"
  />
  <img
    srcset="
      company-photo-256w.jpg   256w,
      company-photo-512w.jpg   512w,
      company-photo-768w.jpg   768w,
      company-photo-1024w.jpg 1024w,
      company-photo-1280w.jpg 1280w
    "
    src="company-photo-256w.jpg"
    sizes="(min-width: 560px) 251px, 88.43vw"
    alt="The Sconnie Timber Staff!"
  />
</picture>

根據個別瀏覽器支援情形,我已將其縮減為以下項目:

<img
  src="/image/sizes:true/company-photo.jpg"
  sizes="(min-width: 560px) 251px, 88.43vw"
  alt="SAY CHEESY PICKLES."
/>

在這個範例中,/image 網址是 PHP 指令碼,後面接著由 mod_rewrite 重寫的參數。它會使用圖片檔案名稱和其他參數,協助後端指令碼在特定條件下選擇最佳圖片。

我猜您第一個問題是「但這不是只是在後端重新實作 <picture>srcset 嗎?」

在某種程度上,是的,但有個重要的區別:當應用程式使用用戶端提示來製作媒體回應時,大部分 (如果不是全部) 的工作都更容易自動化,這可能包括可代您執行這項工作的服務 (例如 CDN)。但 HTML 解決方案需要編寫新的標記,才能提供各種用途。您可以自動產生標記。不過,如果設計或需求有所變更,您日後很可能需要重新檢視自動化策略。

有了用戶端提示,您就能從無損、高解析度的圖片開始,然後動態調整大小,以便針對任何螢幕和版面配置組合進行最佳化。與 srcset 不同的是,這個方法可讓您更靈活地列舉瀏覽器可選的可能圖片候選清單。srcset 會強制您向瀏覽器提供粗略的子類組合,例如 256w512w768w1024w,但採用客戶端提示的解決方案可提供所有寬度,而無需大量標記。

當然,您不必自行編寫圖片選取邏輯。當您使用 w_auto 參數時,Cloudinary 會使用用戶端提示來製作圖片回應,並觀察到使用支援用戶端提示的瀏覽器時,使用者下載的位元組數量中位數減少了 42%。

但請注意,Chrome 67 電腦版的變更已移除對跨來源用戶端提示的支援。幸好,這些限制不會影響 Chrome 行動版,而且在功能政策完成後,這些限制將會全面解除,適用於所有平台。

協助網路速度緩慢的使用者

Adaptive Performance 的概念是,我們可以根據用戶端提示提供的資訊調整資源提供方式,特別是與使用者目前網路連線狀態相關的資訊。

就 Sconnie Timber 的網站而言,我們會在網路速度緩慢時採取措施,減輕負載,並在後端程式碼中檢查 Save-DataECTRTTDownlink 標頭。完成後,我們會產生網路品質分數,用於判斷是否應介入以改善使用者體驗。這個網路分數介於 01 之間,其中 0 是網路品質最差,而 1 是網路品質最佳。

一開始,我們會檢查是否有 Save-Data。如果是,分數就會設為 0,因為我們假設使用者希望我們採取必要措施,讓體驗更輕盈、更快速。

不過,如果沒有 Save-Data,我們會繼續評估 ECTRTTDownlink 提示的值,藉此計算描述網路連線品質的分數。網路評分產生原始碼可在 GitHub 上取得。結論是,如果我們以某種方式使用網路相關提示,就能為網路速度較慢的使用者提供更優質的體驗。

比較未使用用戶端提示來因應網路連線速度緩慢的網站 (左圖),以及使用用戶端提示的相同網站 (右圖)。
圖 2:當地商家網站的「關於我們」頁面。基本體驗包括網路字型、用於驅動輪轉介面和摺疊介面行為的 JavaScript,以及內容圖片。當網路狀況過慢,無法快速載入時,我們可以省略這些項目。

當網站採用客戶端提示提供的資訊時,我們不必採用「全有或全無」的做法。我們可以智慧地決定要傳送哪些資源。我們可以修改回應式圖片選取邏輯,為特定顯示畫面傳送品質較低的圖片,以便在網路品質不佳時加快載入效能。

在這個範例中,我們可以看到客戶端提示對改善較慢網路上的網站效能所產生的影響。以下是 WebPagetest 的瀑布圖,顯示在網路速度較慢且未調整用戶端提示的網站:

WebPagetest 階層圖,顯示 Sconnie Timber 網站在慢速網路連線下載入所有資源的情況。
圖 3:在連線速度緩慢的情況下,資源密集的網站會載入圖片、指令碼和字型。

以下是同一個網站在相同的低速連線上顯示的瀑布圖,但這次網站使用用戶端提示來排除非必要的網頁資源:

Sconnie Timber 網站的 WebPagetest 階層圖,使用用戶端提示決定在網路連線速度緩慢時,不載入非必要資源。
圖 4:同一個連線上的相同網站,只會排除「可有可無」的資源,以便加快載入速度。

客戶端提示將網頁載入時間從超過 45 秒縮短到 不到十分之一。在這種情況下,客戶端提示的優點不容小覷,對於透過緩慢網路尋找重要資訊的使用者來說,更是一大福音。

此外,您可以使用用戶端提示,而不會影響不支援用戶端提示的瀏覽器體驗。舉例來說,如果我們想調整資源提交作業,使用 ECT 提示的值,同時仍為不支援的瀏覽器提供完整體驗,我們可以將回復設定為預設值,如下所示:

// Set the ECT value to "4g" by default.
$ect = isset($_SERVER["HTTP_ECT"]) ? $_SERVER["HTTP_ECT"] : "4g";

在此處,"4g" 代表 ECT 標頭所描述的最高品質網路連線。如果我們將 $ect 初始化為 "4g",則不支援用戶端提示的瀏覽器不會受到影響。選擇採用功能讚!

請注意快取!

只要您根據 HTTP 標頭變更回應,就必須瞭解快取如何處理該資源日後的擷取作業。Vary 標頭在此處不可或缺,因為它會將快取項目的索引鍵連結至所提供要求標頭的值。簡單來說,如果您根據特定 HTTP 要求標頭修改任何回應,幾乎總是應在 Vary 中加入要求該標頭,如下所示:

Vary: DPR, Width

不過,這項做法有一個重大的警告:請勿在經常變更的標頭 (例如 Cookie) 上使用 Vary 快取可快取的回應,因為這些資源實際上會變成無法快取。因此,您可能會想避免在用戶端提示標頭 (例如 RTTDownlink) 上執行 Vary,因為這些是可能經常變更的連線因素。如果您想修改這些標頭的回應,建議只鍵入 ECT 標頭,這樣可盡量減少快取錯誤。

當然,這項做法只適用於您一開始就快取回應的情況。舉例來說,如果 HTML 素材資源的內容為動態內容,您就不會快取這些素材資源,因為這可能會破壞使用者重複造訪的體驗。在這種情況下,您可以視需要修改這類回應,而無須考慮 Vary

服務工作站中的用戶端提示

內容協商功能現在不只適用於伺服器!由於服務工作站會充當用戶端和伺服器之間的 Proxy,您可以控制透過 JavaScript 傳送資源的方式。包括用戶端提示。在服務工作者 fetch 事件中,您可以使用 event 物件的 request.headers.get 方法讀取資源的要求標頭,如下所示:

self.addEventListener('fetch', (event) => {
  let dpr = event.request.headers.get('DPR');
  let viewportWidth = event.request.headers.get('Viewport-Width');
  let width = event.request.headers.get('Width');

  event.respondWith(
    (async function () {
      // Do what you will with these hints!
    })(),
  );
});

您選擇加入的任何用戶端提示標頭都可以以這種方式讀取。不過,這並非取得部分資訊的唯一方式。您可以在 navigator 物件中讀取這些等同的 JavaScript 屬性,以取得特定網路的提示:

用戶端提示 對應的 JS 語言
`ECT` `navigator.connection.effectiveType`
`RTT` `navigator.connection.rtt`
`Save-Data` `navigator.connection.saveData`
`Downlink` `navigator.connection.downlink`
`Device-Memory` `navigator.deviceMemory`
適用於檔案類型的 Imagemin 外掛程式。

由於這些 API 並非在所有地區都適用,因此您需要使用 in 運算子進行功能檢查:

if ('connection' in navigator) {
  // Work with netinfo API properties in JavaScript!
}

從這裡開始,您可以使用與伺服器類似的邏輯,唯一的差別是您「不需要」使用伺服器與用戶端提示協商內容。服務人員可在使用者離線時提供內容,因此能提供更快速、更有彈性的服務。

總結

有了用戶端提示,我們就能以完全漸進的方式,為使用者提供更快速的體驗。我們可以根據使用者的裝置功能提供媒體,讓回應式圖片的服務比依賴 <picture>srcset 更容易,特別是在複雜用途的情況下。這不僅可減少開發方面的時間和精力,還能以比 和 srcset 更精確的方式,針對使用者的螢幕最佳化資源 (尤其是圖片)。

更重要的是,我們可以嗅探網路連線品質不佳的情況,並修改傳送內容和傳送方式,為使用者解決問題。這可以大大提升網站在網路不穩定時的使用便利性。搭配服務工作者,我們就能建立離線可用的超快網站。

雖然用戶端提示僅適用於 Chrome 和以 Chromium 為基礎的瀏覽器,但您可以以不影響不支援用戶端提示的瀏覽器的方式使用用戶端提示。建議您使用用戶端提示,打造真正全面且可調整的體驗,瞭解每位使用者的裝置功能,以及他們連線的網路。希望其他瀏覽器供應商也能看到這些功能的價值,並表達導入意願。

資源

感謝 Ilya GrigorikEric PortisJeff PosnickYoav WeissEstelle Weyl 提供寶貴意見和編輯這篇文章。