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

開發在各地快速載入的網站,有時可能並不容易。大量的裝置功能 (以及連線的網路品質) 都可能使這是一個不可忽視的任務。雖然我們可以利用瀏覽器功能改善載入效能,但如何得知使用者的裝置能力或網路連線的品質?解決方案就是用戶端提示

用戶端提示是一組選擇接受的 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 圖片的內建尺寸為 320x240,2x 圖片的內建函式尺寸為 640x480。如果用戶端在螢幕裝置像素比例為 2 的裝置 (例如 Retina 螢幕) 上安裝的用戶端會剖析這個標記,系統就會要求 2x 圖片。2x 圖片的密度修正內建函式大小為 320x240,因為 640x480 除以 2 是 320x240。

「Extrinsic size」:套用 CSS 和其他版面配置係數 (例如 widthheight 屬性) 後的媒體資源大小。假設您有 <img> 元素可載入像素密度校正 320x240 的圖片,但其中也具有 CSS widthheight 屬性,且分別套用了 256px192px 的值。在這個範例中,該 <img> 元素的延伸大小會變成 256x192。

插圖:內建尺寸與極端大小。大小為 320x240 像素的方塊會顯示,並標有「簡介」大小。其中小方塊尺寸為 256x192 像素,代表套用 CSS 的 HTML img 元素。這個方塊的名稱為「EXTRINSIC size」。右側是一個方塊,其中包含套用 CSS 的元素,該元素會修改 img 元素的版面配置,使其極端大小與內建函式的大小不同。
圖 1:顯示內在體和外在體大小的插圖。套用版面配置因素後,圖片就會增加極端大小。在此情況下,套用 width: 256px;height: 192px; 的 CSS 規則會將內建大小為 320x240 的圖片,轉換成 256 x 192 的極端大小圖片。

一起來看看可用的裝置專屬用戶端提示吧!

可視區域寬度

Viewport-Width 是使用者在 CSS 像素中的可視區域寬度:

Viewport-Width: 320

這項提示可搭配其他螢幕專屬提示,提供不同的圖片處理方式 (例如裁剪),提供最適合特定螢幕大小 (例如藝術方向) 的圖片,或省略對目前螢幕寬度不需要的資源。

DPR

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

DPR: 2

當您選取與螢幕像素密度相對應的圖片來源時,這項提示就非常實用 (就像 x 描述元在 srcset 屬性中的操作一樣)。

寬度

圖片資源要求由 <img><source> 使用 sizes 屬性觸發的圖片資源要求顯示時,會顯示 Width 提示。sizes 會告知瀏覽器資源的極端大小;Width 會使用這種外在尺寸來要求圖片本身採用最適合目前版面配置的大小。

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

Width: 544

在此情況下,用戶端會指示伺服器針對要求的圖片提供最佳內建函式寬度,將 85% 的可視區域寬度 (272 像素) 乘以螢幕 DPR (2),等於 544 像素。

這項提示非常強大,不僅會考量螢幕密度修正後的寬度,還會將這項重要資訊與版面配置中的極端大小結合。如此一來,伺服器就能協商最適合螢幕「和」版面配置的圖片回應。

Content-DPR

雖然您知道「螢幕」有裝置的像素比例,但資源也有其專屬的像素比例。在最簡單的資源選取用途中,裝置和資源之間的像素比例可能會相同。不過,如果 DPRWidth 標頭都處於運作狀態,則資源的延伸大小可能會產生兩者不同的情況。這時 Content-DPR 提示就能派上用場。

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

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

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

裝置記憶體

在技術上,Device-MemoryDevice Memory API 的一部分,因此會顯示目前裝置的近似記憶體 (以 GiB 為單位):

Device-Memory: 2

這項提示的可能用途是,在記憶體有限的裝置上,減少向瀏覽器傳送 JavaScript 的數量,因為 JavaScript 是佔用資源最多資源的內容類型。或者,您可以傳送較低的 DPR 圖片,因為這類圖片會耗用較少記憶體進行解碼。

網路提示

Network Information API 提供另一種類別為描述使用者網路連線效能的用戶端提示。我認為這是最實用的提示組合。透過變更,我們得以變更在連線速度緩慢時為用戶端提供資源的方式,為使用者提供量身打造的體驗。

即時文字訊息

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

RTT: 125

這項提示很實用,因為延遲在載入效能中扮演的角色。透過使用 RTT 提示,我們可根據網路回應能力做出決策,加快整體體驗的提供速度 (例如省略部分要求)。

雖然延遲時間對載入效能來說很重要,但頻寬也很重要。Downlink 提示 (以每秒百萬位元數 (Mbps) 表示) 表示,會顯示使用者連線的「概略」下游速度:

Downlink: 2.5

DownlinkRTT 搭配使用,可以根據網路連線品質變更內容傳遞給使用者的方式。

厄瓜多時間

ECT 提示代表「Effective Connection Type」。其值是連線類型的列舉清單之一,每個清單說明同時指定 RTTDownlink 值之間的連線

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

ECT: 2g

ECT 的有效值為 4g3g2gslow-2g。此提示可做為評估連線品質的起點,隨後使用 RTTDownlink 提示加以修正。

節省數據流量

Save-Data 並不是說明網路條件的提示,因為這是使用者偏好,表示頁面要傳送的資料量較少。

我偏好將 Save-Data 分類為網路提示,因為要執行的操作很多,與其他網路提示相似。使用者可能也會在高延遲/低頻寬環境中啟用這項服務。此提示 (如果有的話) 一律如下所示:

Save-Data: on

Google 已經說明瞭 Save-Data 的用途。這項技術對成效的影響可能十分深遠。使用者要求你減少傳送內容的次數!傾聽這些信號並採取行動時,使用者會感到開心。

融會貫通、靈活運用

至於要如何應用用戶端提示,則取決於您。您有很多選擇為了尋求幾個靈感,我們先來看看位於中西部南部的虛構木材公司 Sconnie Timber 可以採取哪些行動。就像在偏遠地區的情況一樣,網路連線可能會很脆弱。這時用戶端提示等技術就能 為使用者帶來實際的影響

回應式圖片

除了最簡便的回應式圖片用途外,可能還需要複雜。如果您針對不同螢幕大小和不同格式,對同一張圖片套用多種實驗組「和」變化版本,該怎麼辦?這種標記非常相當相當如此。很容易出錯,且很容易忘記或誤解重要概念 (例如 sizes)。

雖然 <picture>srcset 是不可否認的「太棒了」工具,但針對複雜的用途進行開發和維護可能相當耗時。我們可以自動產生標記,但這樣做也困難重重,因為 <picture>srcset 的功能十分複雜,自動化功能需要在保有靈活性的情況下完成自動化。

用戶端提示可以簡化這項作業。透過用戶端提示協商圖片回應看起來可能像這樣:

  1. 如果您的工作流程適用,請先查看 Viewport-Width 提示,選擇要使用的圖片處理方式 (例如圖片導向圖像)。
  2. 檢查 Width 提示和 DPR 提示,並選擇符合圖片版面配置大小和螢幕密度的來源,即可選取圖片解析度 (類似於 srcsetxw 描述元的運作方式)。
  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),但用戶端提示解決方案可在沒有大量標記的情況下提供所有寬度。

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

但請小心!Chrome 67 電腦版中的變更不再支援跨來源用戶端提示。幸好,這些限制不會影響 Chrome 行動版,而且只要遵守功能政策,所有平台都會全面解除這些限制。

為網路速度緩慢時提供協助的使用者

「自動調整效能」是指我們可以根據用戶端提示,調整我們提供資源的方式,特別是有關使用者網路連線目前狀態的資訊。

針對 Sconnie Timber 的網站所考量的部分,我們會在網路速度緩慢時採取措施來減輕負載,並在後端程式碼中排除 Save-DataECTRTTDownlink 標頭。執行此動作後,我們會產生網路品質分數,用來判斷是否應介入以提供更好的使用者體驗。這個網路分數介於 01 之間,其中 0 是最低的網路品質,而 1 則是最高分。

一開始,我們會檢查 Save-Data 是否存在。如果為的話,分數會設為 0,因為我們會假設使用者想採取任何必要行動,讓體驗更輕薄、速度更快。

但如果缺少 Save-Data,我們會繼續努力並權 ECTRTTDownlink 提示的值,然後計算一個描述網路連線品質的分數。您可以在 GitHub 上取得網路分數產生原始碼。重點在於,如果「以某些」方式使用網路相關提示,就能讓網路速度緩慢的使用者享有更優質的體驗。

比較兩個網站未使用用戶端提示,以適應網路連線速度緩慢 (左側) 與相同網站 (右側) 的情況。
圖 2:當地商家網站的「關於我們」頁面基本體驗包括網頁字型、用於調整輪轉介面和手持行為的 JavaScript,以及內容圖片。當網路條件速度過慢而無法快速載入時,我們可以忽略這些這些訊息。

如果網站根據用戶端提示提供的資訊,我們就不必採取「全部或空」的做法。我們可以依據自身需求判斷要傳送哪些資源我們可以修改回應式圖片選取邏輯,為特定螢幕傳送畫質較低的圖片,在網路品質不佳時加快載入速度。

在本例中,我們可以看到用戶端提示在改善網路速度較慢時,網站效能可能造成的影響。下方是網站所在速度緩慢的 WebPagetest 刊登序列,無法依照用戶端提示進行調整:

Sconnie Timber 網站的 WebPagetest 瀑布,會在網路連線速度緩慢時載入所有資源。
圖 3:在連線速度緩慢的情況下,需要大量資源載入網站的圖片、指令碼和字型。

現在同一個網站的刊登序列也同樣緩慢的連線速度緩慢,除了這次,網站會使用用戶端提示來排除不重要的網頁資源:

Sconnie Timber 網站的 WebPagetest 刊登序列,利用用戶端提示決定不在網路連線速度緩慢時載入非重要資源。
圖 4:使用相同連線的網站,只有排除「沒問題」的資源,才能加快載入速度。

用戶端提示將網頁載入時間從 45 秒以上縮短至不到 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 可快取的回應,因為這些資源無法有效快取。瞭解這一點後,建議您避免對用戶端提示標頭 (例如 RTTDownlink) 使用 Vary,因為這些都是可能經常變更的連線因素。如要修改這些標頭的回應,建議您只輸入 ECT 標頭,以盡量減少快取失敗次數。

當然,這僅適用於在一開始快取回應時。舉例來說,如果是動態的 HTML 素材資源,您將不會快取 HTML 素材資源,因為這可能會破壞再次造訪時的使用者體驗。在這些情況下,您可以視需要根據自己的需求修改這類回應,無須擔心 Vary 的問題。

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」
「儲存資料」 `navigator.connection.saveData`
`Downlink` 「navigator.connection.downlink」
「裝置記憶體」 「navigator.deviceMemory」
Imagemin 外掛程式適用於檔案類型。

這些 API 不適用於您需要透過 in 運算子進行功能檢查的地方:

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

接著,您可以依照在伺服器使用的方式使用類似邏輯,但不需要伺服器與用戶端提示協商內容。因為服務工作者可以在使用者離線時提供額外的功能,因此能更快速且靈活地提供內容。

設定程序即將完成

透過用戶端提示,我們能夠以漸進的方式為使用者快速提供更快速的體驗。我們可以根據使用者的裝置功能提供媒體,使其比使用 <picture>srcset 更容易提供回應式圖片,尤其是針對複雜的用途。這不僅讓我們不僅能減少開發端的時間和人力,還能夠以比 和 srcset 更精細的定位使用者螢幕的方式,對資源 (尤其是圖片) 進行最佳化調整。

或許更重要的是,我們可以透過修改傳送的內容和傳送方式,找出連線品質不佳的網路連線,並縮短使用者的差距。這樣做可以帶來「長久一點」,讓另一個脆弱網路的使用者更容易存取網站。結合 Service Worker 後,我們就能建立速度飛快且可離線使用的網站。

雖然用戶端提示僅適用於 Chrome (和以 Chromium 為基礎的瀏覽器),但能以不易支援的瀏覽器使用這類提示。請考慮使用用戶端提示,打造完全多元包容且有彈性的體驗,意識到每位使用者的裝置功能以及連線的網路。希望其他瀏覽器供應商也能看到他們的價值,並決定實作的意圖。

資源

感謝 Ilya GrigorikEric PortisJeff PosnickYoav WeissEstelle Weyl,對本文章的寶貴意見和編輯內容。