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

Ilya Grigorik
Ilya Grigorik

專為網站打造,能讓您的觸及率無與倫比。只要按一下滑鼠,幾乎適用於幾乎所有連上網路的裝置、平板電腦、平板電腦、筆記型電腦、桌上型電腦、電視,以及各種品牌或平台,只要輕輕一按就能存取網頁應用程式。為提供最佳體驗,您打造了回應式網站,可配合每種板型規格調整呈現方式和功能,現在您要執行效能檢查清單,確保應用程式能盡快載入:您已經對關鍵轉譯路徑進行最佳化、壓縮及快取文字資源,而且現在經常檢查圖片資源中傳輸的位元組。問題是,圖片最佳化很難

  • 決定適當的格式 (向量與光柵)
  • 決定最佳的編碼格式 (jpeg、webp 等)
  • 決定適當的壓縮設定 (有損和無損的)
  • 決定要保留或去除哪些中繼資料
  • 為每個螢幕製作多個變化版本 + 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. 為了達到最佳視覺品質,她需要為每張圖片產生多種不同解析度的變化版本,這些變化版本包括:1 倍、1.5 倍、2 倍、2.5 倍、3 倍。
  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 的支援。

接下來的三個要求標頭是用戶端提示標頭,用於公告用戶端裝置 (3 倍) 的裝置像素比例、版面配置可視區域寬度 (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">

還有,還記得上面這位男士嗎?用戶端提示讓 Humble 圖片標記不再採用任何其他標記,因此現已改為 DPR、可視區域和寬度。如果需要新增藝術方向,可以使用 picture 標記 (如上文所示),否則所有現有圖片標記都變得更聰明瞭。用戶端提示可強化現有的 imgpicture 元素。

使用 Service Worker 掌控資源選取作業

ServiceWorker 實際上是在瀏覽器中執行的用戶端 Proxy。這個 API 會攔截所有傳出要求,且可讓您檢查、重寫、快取,甚至合成回應。圖片並無不同,啟用用戶端提示後,運作中的 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. <insert my favorite hint>怎麼辦? ServiceWorker 可讓開發人員攔截及修改 (例如新增標頭) 所有傳出要求。例如,您可以新增以 NetInfo 為基礎的資訊,來表示目前的連線類型,詳情請參閱「使用 ServiceWorker 建立功能報告」一文。瀏覽器會導入 Chrome 中傳送的「原生」提示 (DPR、寬度、資源寬度),因為單純的 SW 實作會導致所有圖片要求延遲。

  7. 哪裡可以找到更多資訊、查看更多示範操作?該怎麼處理? 請參閱說明文件。如有任何意見或其他問題,歡迎在 GitHub 上開啟問題