使用用戶端提示自動選取資源

Ilya Grigorik
Ilya Grigorik

建構網站可讓您觸及更多觀眾。只要點選一下,即可使用網路應用程式,而且幾乎所有連網裝置 (智慧型手機、平板電腦、筆電、電腦、電視等) 皆可使用,不限品牌或平台。為了提供最佳體驗,您已建立回應式網站,可根據每種介面形態調整呈現方式和功能,現在您正在執行效能檢查清單,確保應用程式能盡可能快速載入:您已最佳化關鍵轉譯路徑壓縮及快取文字資源,現在您正在查看圖片資源,因為這類資源通常占了大部分傳輸位元組。問題是,圖片最佳化很難

  • 決定適當的格式 (向量或光柵)
  • 決定最佳的編碼格式 (JPEG、WebP 等)
  • 決定正確的壓縮設定 (有損 vs. 無損)
  • 決定要保留或移除哪些中繼資料
  • 為每個螢幕 + DPR 解析度建立多個變化版本
  • ...
  • 考量使用者的網路類型、速度和偏好設定

這些問題各自都瞭若指掌。這些因素加總起來,就會產生我們 (開發人員) 經常忽略或忽視的龐大最佳化空間。人類不擅長重複探索相同的搜尋空間,尤其是涉及許多步驟時。另一方面,電腦擅長執行這類工作。

要為圖片和其他類似屬性的資源採用良好且可持續的最佳化策略,答案很簡單:自動化。如果您手動調整資源,那麼您做錯了:您會忘記、會變得懶惰,或是其他人會犯下這些錯誤 - 保證如此。

效能導向開發人員的傳奇故事

透過圖片最佳化空間搜尋有兩個不同的階段:建構時間和執行時間。

  • 有些最佳化方式是資源本身固有的,例如選取適當的格式和編碼類型、調整每個編碼器的壓縮設定、移除不必要的中繼資料等等。這些步驟可以在「建構時間」執行。
  • 其他最佳化功能則取決於要求的用戶端類型和屬性,且必須在「執行階段」執行:為用戶端的 DPR 和預期顯示寬度選取適當的資源,並考量用戶端的網路速度、使用者和應用程式偏好設定等等。

雖然有建構時工具,但仍有改善空間。舉例來說,動態調整每張圖片和每種圖片格式的「品質」設定可以節省大量空間,但我還沒看到有人在研究之外實際使用這項功能。這正是創新的絕佳領域,但為了這篇文章的目的,我會就此打住。我們來著重於故事的執行階段。

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

應用程式意圖很簡單:擷取並顯示圖片,占用使用者檢視區的 50%。這是大多數設計師洗手的地方,也是前往酒吧的必經之路。與此同時,團隊中注重效能的開發人員也忙著處理一連串問題:

  1. 為了獲得最佳壓縮效果,她希望為每個用戶端使用最佳圖片格式:Chrome 使用 WebP、Edge 使用 JPEG XR,其他則使用 JPEG。
  2. 為了獲得最佳視覺品質,她需要以不同解析度產生每張圖片的多種變化版本:1x、1.5x、2x、2.5x、3x,甚至可能還有更多。
  3. 為了避免提交不必要的像素,她需要瞭解「使用者檢視區 50% 實際代表什麼」,因為市面上有許多不同的檢視區寬度!
  4. 她也希望提供彈性體驗,讓網路速度較慢的使用者自動擷取較低解析度的內容。畢竟,現在是玻璃的時代。
  5. 應用程式也會公開一些使用者控制項,影響應擷取哪些圖片資源,因此也必須納入考量。

接著,設計師發現,如果可視區大小較小,她需要以 100% 的寬度顯示其他圖片,以便提高可讀性。這表示我們現在必須為另一個素材資源重複相同的程序,然後根據可視區域大小設定擷取條件。我有提到這類內容很難嗎?好,那就開始吧。picture 元素可讓我們取得相當多的資訊

<picture>
    <!-- serve WebP to Chrome and Opera -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.webp 200w, /image/thing-400.webp 400w,
        /image/thing-800.webp 800w, /image/thing-1200.webp 1200w,
        /image/thing-1600.webp 1600w, /image/thing-2000.webp 2000w"
    type="image/webp">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.webp 200w, /image/thing-crop-400.webp 400w,
        /image/thing-crop-800.webp 800w, /image/thing-crop-1200.webp 1200w,
        /image/thing-crop-1600.webp 1600w, /image/thing-crop-2000.webp 2000w"
    type="image/webp">
    <!-- serve JPEGXR to Edge -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpgxr 200w, /image/thing-400.jpgxr 400w,
        /image/thing-800.jpgxr 800w, /image/thing-1200.jpgxr 1200w,
        /image/thing-1600.jpgxr 1600w, /image/thing-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpgxr 200w, /image/thing-crop-400.jpgxr 400w,
        /image/thing-crop-800.jpgxr 800w, /image/thing-crop-1200.jpgxr 1200w,
        /image/thing-crop-1600.jpgxr 1600w, /image/thing-crop-2000.jpgxr 2000w"
    type="image/vnd.ms-photo">
    <!-- serve JPEG to others -->
    <source
    media="(min-width: 50em)"
    sizes="50vw"
    srcset="/image/thing-200.jpg 200w, /image/thing-400.jpg 400w,
        /image/thing-800.jpg 800w, /image/thing-1200.jpg 1200w,
        /image/thing-1600.jpg 1600w, /image/thing-2000.jpg 2000w">
    <source
    sizes="(min-width: 30em) 100vw"
    srcset="/image/thing-crop-200.jpg 200w, /image/thing-crop-400.jpg 400w,
        /image/thing-crop-800.jpg 800w, /image/thing-crop-1200.jpg 1200w,
        /image/thing-crop-1600.jpg 1600w, /image/thing-crop-2000.jpg 2000w">
    <!-- fallback for browsers that don't support picture -->
    <img src="/image/thing.jpg" width="50%">
</picture>

我們已處理藝術指導、格式選項,並提供每張圖片的六種變化版本,以因應客戶裝置的 DPR 和檢視區寬度變化。太棒了!

很抱歉,picture 元素不允許我們定義任何規則,說明該元素應如何根據用戶端的連線類型或速度運作。不過,在某些情況下,其處理演算法會允許使用者代理程式調整所擷取的資源,請參閱步驟 5。我們只能希望使用者代理程式夠聰明。(注意:目前沒有任何實作項目是這樣做)。同樣地,picture 元素中也沒有鉤子,因此無法允許應用程式專屬邏輯,以便考量應用程式或使用者偏好設定。為了取得這兩個最後的位元,我們必須將上述所有邏輯移至 JavaScript,但這樣一來,就會失去 picture 提供的預先載入掃描器最佳化功能。嗯。

除了這些限制之外,這項功能運作良好。至少對這個特定素材資源而言是如此。真正的長期挑戰是,我們無法要求設計師或開發人員為每個素材資源手動編寫這類程式碼。這款遊戲在第一次嘗試時很有趣,但之後就會失去吸引力。我們需要自動化功能。或許 IDE 或其他內容轉換工具可以幫上忙,自動產生上述範本。

使用用戶端提示自動選取資源

深呼吸,暫時放下你的懷疑,現在請考慮以下範例:

<meta http-equiv="Accept-CH" content="DPR, Viewport-Width, Width">
...
<picture>
    <source media="(min-width: 50em)" sizes="50vw" srcset="/image/thing">
    <img sizes="100vw" src="/image/thing-crop">
</picture>

信不信由您決定,但上述範例提供的功能與上述較長的圖片標記相同,而且,正如我們將要看到的,它還可讓開發人員完全控管擷取圖片資源的方式、來源和時間。第一行中的「魔法」可啟用用戶端提示回報功能,並告知瀏覽器向伺服器宣傳裝置像素比例 (DPR)、版面配置視區寬度 (Viewport-Width) 和資源的預期顯示寬度 (Width)。

啟用用戶端提示後,產生的用戶端標記只會保留呈現需求。設計師不必擔心圖片類型、用戶端解析度、用於減少傳送位元組的最佳中斷點,或其他資源選取條件。說實話,他們從未這樣做,也不應如此。更棒的是,開發人員也不需要重寫及擴充上述標記,因為實際資源選取是由用戶端和伺服器協商而定。

Chrome 46 提供 DPRWidthViewport-Width 提示的原生支援功能。提示預設為停用,而上述的 <meta http-equiv="Accept-CH" content="..."> 則做為選擇加入信號,可告知 Chrome 將指定的標頭附加至傳出的要求。完成設定後,我們來檢查圖片要求的範例要求和回應標頭:

用戶端提示協商圖表

Chrome 會透過 Accept 要求標頭宣告支援 WebP 格式;新版 Edge 瀏覽器也會透過 Accept 標頭宣告支援 JPEG XR。

接下來的三個要求標頭是用戶端提示標頭,用於宣傳用戶端裝置的裝置像素比 (3x)、版面配置可視區域寬度 (460 像素),以及資源的預期顯示寬度 (230 像素)。這會向伺服器提供所有必要資訊,讓伺服器根據其自身的政策組合選取最佳圖片變化版本:預先產生的資源是否可用、重新編碼或調整資源大小的成本、資源的熱門程度、目前的伺服器負載等等。在這個特定情況下,伺服器會使用 DPRWidth 提示,並傳回 WebP 資源,如 Content-TypeContent-DPRVary 標頭所示。

這並非神奇之處。我們將資源選取作業從 HTML 標記移至用戶端和伺服器之間的請求-回應協商程序。因此,HTML 只會處理呈現需求,且任何設計師和開發人員都能編寫,而透過圖片最佳化空間進行搜尋的作業則會交由電腦處理,現在也能輕鬆進行大規模自動化。還記得我們之前介紹的注重效能的開發人員嗎?她現在的工作是編寫圖片服務,以便利用提供的提示並傳回適當的回應:她可以使用任何語言或伺服器,也可以讓第三方服務或 CDN 代為執行這項作業。

<img src="/image/thing" sizes="50vw"
        alt="image thing displayed at 50% of viewport width">

另外,還記得上面這位人物嗎?有了用戶端提示,原本不起眼的圖片標記現在就能辨識 DPR、視區和寬度,而且不需要任何額外的標記。如果您需要新增 art-direction,可以使用 picture 標記,如上所述,這樣所有現有的圖片標記都會變得更聰明。用戶端提示可強化現有的 imgpicture 元素。

使用服務工作者控管資源選取作業

ServiceWorker 實際上是瀏覽器中執行的用戶端 Proxy。它會攔截所有傳出要求,並讓您檢查、重寫、快取,甚至合成回應。圖片也是如此,在啟用用戶端提示後,處於活動狀態的 ServiceWorker 就能識別圖片要求、檢查提供的用戶端提示,並定義自己的處理邏輯。

self.onfetch = function(event) {
    var req = event.request.clone();
    console.log("SW received request for: " + req.url)
    for (var entry of req.headers.entries()) {
    console.log("\t" + entry[0] +": " + entry[1])
    }
    ...
}
用戶端提示 serviceWorker。

ServiceWorker 可讓您在用戶端端控管資源選取。這一點非常重要。請先消化這個概念,因為可能性幾乎是無窮無盡的:

  • 您可以重寫使用者代理程式設定的用戶端提示標頭值。
  • 您可以將新的客戶端提示標頭值附加至要求。
  • 您可以重寫網址,並將圖片要求指向其他伺服器 (例如 CDN)。
    • 您甚至可以將提示值從標頭移至網址本身,這樣在基礎架構中部署時會更輕鬆。
  • 您可以快取回應,並定義提供資源的邏輯。
  • 您可以根據使用者的連線情形調整回應內容。
  • 您可以考量應用程式和使用者偏好設定的覆寫值。
  • 你可以做任何你想做的事,真的。

picture 元素可在 HTML 標記中提供必要的藝術方向控制項。用戶端提示會在產生的圖片要求中提供註解,以便自動選取資源。ServiceWorker 會在用戶端提供要求和回應管理功能。這就是可擴充的網路的實際應用。

用戶端提示常見問題

  1. 用戶端提示可用於哪些位置?Chrome 46 隨附。目前正在考慮在 FirefoxEdge 中實施。

  2. 為什麼要選擇啟用用戶端提示?我們希望盡可能降低不使用用戶端提示的網站所需的額外負擔。如要啟用用戶端提示,網站應在網頁標記中提供 Accept-CH 標頭或等效的 <meta http-equiv> 指令。在上述任一情況下,使用者代理程式會在所有子資源要求中附加適當的提示。日後,我們可能會提供額外機制,讓您為特定來源保留這項偏好設定,以便在導覽要求中提供相同的提示。

  3. 如果有 ServiceWorker,為什麼還需要客戶端提示?ServiceWorker 無法存取版面配置、資源和可視區域寬度資訊。至少不會引入成本高昂的來回傳輸,並大幅延遲圖片要求,例如當圖片要求由預先載入剖析器啟動時。用戶端提示會與瀏覽器整合,讓這項資料可用於要求。

  4. 用戶端提示是否僅適用於圖片資源?DPR、Viewport-Width 和 Width 提示背後的核心用途,是為圖片素材資源啟用資源選取功能。不過,無論類型為何,所有子資源都會收到相同的提示,例如 CSS 和 JavaScript 要求也會取得相同的資訊,並可用於最佳化這些資源。

  5. 為什麼某些圖片要求不會回報寬度?由於網站依賴圖片的內在大小,因此瀏覽器可能不知道預期的顯示寬度。因此,對於這類要求和沒有「顯示寬度」的請求 (例如 JavaScript 資源),系統會略過寬度提示。如要接收寬度提示,請務必在圖片上指定大小值

  6. 如果是 <插入我最愛的提示> 呢?ServiceWorker 可讓開發人員攔截及修改所有傳出的要求 (例如新增新的標頭)。舉例來說,您可以輕鬆新增以 NetInfo 為基礎的資訊,用來指出目前的連線類型。請參閱「使用 ServiceWorker 回報功能」。由於純粹以 SW 為基礎的實作會延遲所有圖片要求,因此 Chrome 會在瀏覽器中實作 Chrome 中提供的「原生」提示 (DPR、寬度、資源寬度)。

  7. 我想進一步瞭解這項服務,哪裡可以找到更多相關資訊和更多示範? 請參閱說明文件,如果有任何意見回饋或其他問題,歡迎在 GitHub 上提出問題