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

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 上提出問題