使用用戶端提示配合使用者

開發在任何地方都能快速載入的網站,可能是一項棘手的任務。裝置功能繁多,且連線網路品質不一,因此這項工作看似難以完成。雖然我們可以利用瀏覽器功能提升載入效能,但如何得知使用者裝置的功能,或網路連線品質?解決方法是用戶端提示!

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

內容協商是關鍵

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

內容協商的一個例子涉及 Accept 要求標頭。它描述瀏覽器理解的內容類型,伺服器可以使用這些類型來協商回應。如果是圖片要求,Chrome 的 Accept 標頭內容如下:

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

雖然所有瀏覽器都支援 JPEG、PNG 和 GIF 等圖片格式,但 Accept 在這種情況下會指出瀏覽器「也」支援 WebP 和 APNG。有了這項資訊,我們就能為每個瀏覽器協商最佳圖片類型:

<?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-Width 和 Downlink 用戶端提示」的訊息。請不用擔心特定提示本身。我們稍後會說明。

您可以使用任何後端語言設定這些選擇加入標頭。例如,可以使用 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 圖片的固有大小為 320x240,2x 圖片的固有大小為 640x480。如果這個標記是由安裝在螢幕裝置像素比例為 2 的裝置 (例如 Retina 螢幕) 上的用戶端剖析,系統會要求 2x 圖片。2x 圖片的密度校正固有大小為 320x240,因為 640x480 除以 2 是 320x240。

外部大小:套用 CSS 和其他版面配置因素 (例如 width 和 height 屬性) 後的媒體資源大小。假設您有一個 <img> 元素,載入的圖片密度校正固有大小為 320x240,但該元素也套用了 CSS width 和 height 屬性,值分別為 256px 和 192px。在本例中,<img> 元素的外部大小會變成 256x192。

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

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

Viewport-Width

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

Viewport-Width: 320

這項提示可與其他螢幕專屬提示搭配使用,為特定螢幕大小提供最佳圖片處理方式 (例如裁剪,即美術指導),或省略目前螢幕寬度不需要的資源。

DPR

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

DPR: 2

選取與螢幕像素密度對應的圖片來源時,這項提示非常實用 (例如 srcset 屬性中的 x 描述元)。

寬度

Width 提示會顯示在由 <img> 或 <source> 標記使用 sizes 屬性觸發的圖片資源要求上。sizes 會告知瀏覽器資源的外部大小;Width 會使用該外部大小,要求具有最適合目前版面配置的固有大小的圖片。

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

Width: 544

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

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

Content-DPR

雖然您已經知道螢幕有裝置像素比例,但資源也有自己的像素比例。在最簡單的資源選取用途中,裝置和資源之間的像素比例可能相同。於是!如果同時使用 DPR 和 Width 標頭,資源的外部大小可能會導致兩者不同。這時 Content-DPR 提示就能派上用場。

與其他用戶端提示不同,Content-DPR 不是伺服器使用的「要求」標頭,而是伺服器在每次使用 DPR 和 Width 提示選取資源時,「必須」傳送的「回應」標頭。Content-DPR 的值應為下列方程式的結果:

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

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

裝置記憶體

Device-Memory 屬於 Device Memory API 的一部分,會顯示目前裝置的大約記憶體容量 (以 GiB 為單位):

Device-Memory: 2

這項提示的可能用途是減少傳送至記憶體有限裝置上瀏覽器的 JavaScript 數量,因為 JavaScript 是瀏覽器通常會載入的最耗用資源內容類型。或者,您也可以傳送 DPR 較低的圖片,因為這類圖片解碼時使用的記憶體較少。

網路提示

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

即時文字訊息

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

RTT: 125

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

雖然延遲時間對載入效能很重要,但頻寬也有影響。Downlink 提示以每秒百萬位元 (Mbps) 為單位,顯示使用者連線的「大概」下行速度:

Downlink: 2.5

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

ECT

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

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

ECT: 2g

ECT 的有效值為 4g、3g、2g 和 slow-2g。這項提示可做為評估連線品質的起點,隨後可使用 RTT 和 Downlink 提示進行調整。

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 提示,並選擇符合圖片版面配置大小和螢幕密度的來源,藉此選取圖片解析度 (類似於 x 和 w 描述元在 srcset 中的運作方式)。
  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 會強制您為瀏覽器提供一組粗略的變化版本 (例如 256w、512w、768w 和 1024w),但採用用戶端提示的解決方案可以提供所有寬度,而不需要大量的標記。

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

不過請注意,電腦版 Chrome 67 的異動已移除對跨源用戶端提示的支援。幸好,這些限制不會影響行動版 Chrome,而且在功能政策相關作業完成後,所有平台都會全面解除限制。

協助網路速度慢的使用者

「自動調整效能」是指根據用戶端提示提供的資訊,調整資源傳送方式。具體來說,就是使用者網路連線目前狀態的相關資訊。

針對 Sconnie Timber 的網站,我們採取措施,在網路速度緩慢時減輕負載,並在後端程式碼中檢查 Save-Data、ECT、RTT 和 Downlink 標頭。完成後,系統會產生網路品質分數,用來判斷是否應介入,以提供更優質的使用者體驗。這個網路分數介於 0 和 1 之間,0 代表最差的網路品質,1 則代表最佳。

首先,我們會檢查 Save-Data 是否存在。如果是,分數會設為 0,因為我們假設使用者希望我們採取任何必要措施,讓體驗更輕巧快速。

不過,如果沒有 Save-Data,我們會繼續評估 ECT、RTT 和 Downlink 提示的值,計算出描述網路連線品質的分數。網路分數產生原始碼可在 GitHub 取得。重點是,如果我們以「某種」方式使用網路相關提示,就能為網路速度較慢的使用者提供更好的體驗。

比較未使用用戶端提示來適應慢速網路連線的網站 (左),以及使用用戶端提示的相同網站 (右)。
圖 2:當地商家網站的「關於我們」頁面。基準體驗包括網頁字型、用於驅動輪轉介面和手風琴行為的 JavaScript,以及內容圖片。如果網路速度太慢,無法快速載入,我們就可以省略這些內容。

當網站根據用戶端提示提供的資訊進行調整時,我們就不必採用「全有或全無」的做法。我們可以智慧地決定要傳送哪些資源。我們可以修改回應式圖片選取邏輯,在網路品質不佳時,針對特定螢幕傳送畫質較低的圖片,加快載入速度。

在這個範例中,我們可以看到用戶端提示在提升慢速網路上的網站效能時,所產生的影響。以下是網站位於慢速網路,且不支援用戶端提示時的 WebPagetest 瀑布圖:

Sconnie Timber 網站在慢速網路連線上載入所有資源的 WebPagetest 瀑布圖。
圖 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",不支援用戶端提示的瀏覽器就不會受到影響。選擇加入 FTW!

留意那些快取!

每當您根據 HTTP 標頭變更回應時,請務必留意快取日後擷取該資源的方式。Vary 標頭在這裡不可或缺,因為它會將快取項目鍵入提供給它的要求標頭值。簡單來說,如果您根據指定的 HTTP 要求標頭修改任何回應,幾乎一律應在 Vary 中加入該標頭,如下所示:

Vary: DPR, Width

不過,這有一個重大限制:您不應在經常變更的標頭 (例如 Cookie) 上 Vary 可快取的回應,因為這些資源實際上會變成無法快取。因此,建議您避免在 RTT 或 Downlink 等用戶端提示標頭上執行 Vary,因為這些連線因素可能會經常變更。如要修改這些標頭的回應,建議只對 ECT 標頭進行鍵控,盡可能減少快取失敗。

當然,這只適用於一開始就快取回應的情況。舉例來說,如果 HTML 素材資源的內容是動態內容,您就不會快取這些素材資源,因為這可能會破壞使用者重複造訪時的體驗。在這種情況下,請根據你認為必要的依據修改這類回覆,不必擔心 Vary。

Service Worker 中的用戶端提示

內容協商不再只是伺服器的專利!由於 Service Worker 扮演用戶端和伺服器之間的 Proxy,因此您可以控制如何透過 JavaScript 傳遞資源。包括用戶端提示。在 Service Worker 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!
}

在這裡,您可以使用與伺服器類似的邏輯,但不需要伺服器與用戶端提示協商內容。Service Worker 具備在使用者離線時提供內容的能力,因此單靠 Service Worker 就能讓體驗更快速、更具彈性。

總結

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

更重要的是,我們可以偵測網路連線不佳的情況,並透過修改傳送內容和傳送方式,為使用者彌補差距。這對網路不穩定的使用者來說,能大幅提升網站存取便利性。結合 Service Worker,我們就能建立速度極快且可離線使用的網站。

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

資源

感謝 Ilya Grigorik、Eric Portis、Jeff Posnick、Yoav Weiss 和 Estelle Weyl 對本文提供的寶貴意見和編輯。