| 導語 在網頁資源加載優化的路上,相信大家對 lazy loading
一點不陌生。可喜可賀的是,<img> 和 <iframe> 上將新增了 loading
屬性,而且已發布的 Chrome 75 版本支持了該特性。四月份社區剛發布這個消息的時候,當時跟進了下。關于這個話題,之前看過幾篇不錯的文章,本文主要是對 "Hybrid Lazy Loading: A Progressive Migration To Native Lazy Loading" 的翻譯。不是為作者的 vanilla-lazyload
打 call 哈,我之前沒用過他這個。
建議閱讀:
【1】、Native image lazy-loading for the web
【2】、Hybrid Lazy Loading: A Progressive Migration To Native Lazy Loading
本文翻譯的是文章【2】,習慣看英文文檔的,建議直接看原文。這里主要是做一下整理和以后碰到類似需求,可以參考下。另外,本人翻譯水平有限,難免有翻譯不恰當的地方。我盡量表達通順,沒必要翻譯的詞,適當加下注解。
1、前言
原生 Lazy-Loading 即將到來。然而,它不像我們之前實現 Lazy-Loading 的方式,它的處理是革命性的,因為其不依賴于 JavaScript,而且能讓我們用起來更方便。但是問題來了,這個特性我們沒法 polyfill(一段代碼或者插件, 提供了那些開發者們希望瀏覽器原生提供支持的功能),而且要得到所有瀏覽器廠商的支持,還將有一段時間。通過這篇文章,你將了解到原生 Lazy-Loading 是怎么工作的,并且能了解到如何逐步選擇性地取代之前 JavaScript 驅動下的 Lazy-Loading。感謝 Hybrid(混合模式) Lazy Loading。
前幾周,你可能已經聽說了原生 Lazy-Loading 將在幾個月后的 Chromium 75 版本里支持(75 版本已經上線了)。
“耶,好消息,但我們必須等到所有瀏覽器都支持它才行。”
如果這個想法在你腦海中出現了,那繼續讀這篇文章。我將嘗試說服你反其道而行之。讓我們開始吧,先對比下原生 Lazy-Loading 和 JavaScript 驅動的方式。
2、Native VS JavaScript-Driven Lazy Loading
Lazy-Loading 是一個提高網站和 Web 應用性能的方式,它通過延遲加載視窗外的內容來最大化加快視窗內的圖片和 Iframe(和視頻)的渲染速度。
2.1、JAVASCRIPT-DRIVEN LAZY LOADING
為了延遲加載圖片或 Iframe,一般常用的處理方式就是通過用類似的數據屬性 `data-src` 來替換 `src` 屬性,標記好后,然后用 JS 去檢測圖片或 Iframe 是不是快接近可視區域了(比如往下滾動屏幕的時候),然后去拷貝數據屬性 `data-src` 的值到 `src` 上,然后觸發那些延遲加載的內容。
<img data-src="turtle.jpg" alt="Lazy turtle" class="lazy">
2.2、NATIVE LAZY LOADING
根據[原生 Lazy-Loading 規范](http://github.com/whatwg/html/pull/3752/),如果你想通過原生的這個特性延遲加載圖片或 Iframe 的話,你僅僅只需要添加 `loading=lazy` 屬性在相關的標簽上即可。
<img src="turtle.jpg" alt="Lazy turtle" loading="lazy">
Addy Osmani 寫了一些關于這個主題的文章 “[Native Image Lazy-Loading For The Web!](https://addyosmani.com/blog/lazy-loading/)”,提到了 Google Chrome 團隊已經在開發這個特性,并試圖加入 Chrome 75 的版本里。
其他基于 Chromium 的瀏覽器,比如 Opera 和 Microsoft Edge 都將受益于此,在基于 Chromium 75 的更新后,同樣會支持這個特性。一起期待吧。
2.2、NATIVE LAZY LOADING
根據[原生 Lazy-Loading 規范](http://github.com/whatwg/html/pull/3752/),如果你想通過原生的這個特性延遲加載圖片或 Iframe 的話,你僅僅只需要添加 `loading=lazy` 屬性在相關的標簽上即可。
<img src="turtle.jpg" alt="Lazy turtle" loading="lazy">
Addy Osmani 寫了一些關于這個主題的文章 “[Native Image Lazy-Loading For The Web!](https://addyosmani.com/blog/lazy-loading/)”,提到了 Google Chrome 團隊已經在開發這個特性,并試圖加入 Chrome 75 的版本里。
其他基于 Chromium 的瀏覽器,比如 Opera 和 Microsoft Edge 都將受益于此,在基于 Chromium 75 的更新后,同樣會支持這個特性。一起期待吧。
2.2.1、Get Started With Native Lazy Loading
為了防止你網站所有的圖片,在頁面打開的時候被一次性全部加載完,你可以嘗試在你網站上開啟(支持的話)原生的 Lazy-Loading,通過簡單地添加一個 HTML 屬性即可。`loading` 屬性會告訴瀏覽器,哪些圖片非常重要,需要立即加載,哪些可以在頁面滾動時再延遲加載。同樣,`loading` 屬性也可以加在 Iframe 上。
為了告訴瀏覽器哪些圖片非常重要,需要立即加載,你必須添加 `loading="eager"` 屬性在 `img` 標簽上。最好是把這個圖片放到最開始的可視范圍內。
<img src="rabbit.jpg" alt="Fast rabbit" loading="eager">
如果要讓瀏覽器延遲加載某些圖片,可以添加 `loading="lazy"` 屬性。尤其是這些圖片一開始不在可視范圍內,建議你這么處理。
<img src="turtle.jpg" alt="Lazy turtle" loading="lazy">
僅僅通過添加 `loading` 屬性在你的圖片和 Iframe 上,你就可以在你的網站啟用原生 Lazy-Loading。你的網站將會慢慢受益于它,因為以后越來越多瀏覽器會支持這個特性。
如果你的網站還沒有使用任何延遲加載的技術來做一些優化,這個方式對你就最好不過了。但是如果你已經通過 JavaScript 驅動的延遲加載模式在處理了,你可能想保持它,同時逐步切換為原生的 Lazy-Loading 模式。
理想的解決方案,就是立即開始使用原生 Lazy-Loading,同時使用 polyfill 來使得所有的瀏覽器都可以生效。不幸的是,原生 Lazy-Loading 不是一個我們可以通過 JavaScript 可以 polyfill 的特性。
2.2.2、No Use For A Polyfill
當一個新的瀏覽器技術在某一個單獨的瀏覽器發布后,開源社區通常都會發布一個 JavaScript Polyfill 給其他的瀏覽器,以此提供同樣的支持。比如 `[IntersectionObserver](https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver)` polyfill,使用 JavaScript 和 DOM 元素,再結合下 `Element.getBoudingClientRect()`,即可實現其原生的 API 功能。
原生 Lazy-Loading 不同的是,因為一個 `loading="lazy"` 的 JavaScript polyfill,需要盡可能快地阻止瀏覽器加載這些標記了屬性的內容。JavaScript 沒法控制頁面初始渲染階段,因此原生 Lazy-Loading 的 polyfill 基本不可能實現了。
3、Hybrid Lazy Loading
如果你不滿足于使用原生 Lazy-Loading 只是作為一種漸進式增強模式,或者你已經實現了基于 JS 的 Lazy-Loading,而且不想在少數瀏覽器上失去這個特性(同時又希望能在支持了原生 Lazy-Loading 的瀏覽器上啟用原生的),那么你就需要一個不同的解決方案。下面介紹下 Hybrid Lazy Loading。
Hybrid Lazy Loading,顧名思義,如果瀏覽器支持原生 Lazy-Loading 的話,則使用原生的,否則,用 JS 來實現 Lazy-Loading
為了實現混合模式的 Lazy-Loading,你需要使用 `data` 屬性來標記你需要延遲加載的內容(和 JS 驅動的 Lazy-Loading 一樣),同時添加 `loading="lazy"` 屬性。
<img data-src="turtle.jpg" loading="lazy" alt="Lazy turtle">
然后你需要寫一些 JS. 在頁面開始加載的時候,你需要檢測瀏覽器是否支持原生的 Lazy-Loading。然后對標記了 `loading="lazy"` 屬性的所有元素進行如下操作:
如果支持原生的 Lazy-Loading,拷貝
data-src
屬性的值到src
屬性上;-
如果不支持,初始化一個 JS Lazy-Loading 的腳本或插件,當這些元素進入可視區域的時候;
你自己實現這些邏輯并不難。你通過如下條件判斷,即可檢測瀏覽器是否支持原生 Lazy-Loading:
if ('loading' in HTMLImageElement.prototype)
如果支持,把 `data-src` 的值拷貝到 `src` 上即可。如果不支持,就初始化你需要的 Lazy-Loading 腳本。
如下是處理這個的代碼片段:
<!-- In-viewport images should be loaded normally, or eagerly -->
<img src="important.jpg" loading="eager" alt="Important image">
<!-- Let’s lazy-load the rest of these images -->
<img data-src="lazy1.jpg" loading="lazy" alt="Lazy image 1">
<img data-src="lazy2.jpg" loading="lazy" alt="Lazy image 2">
<img data-src="lazy3.jpg" loading="lazy" alt="Lazy image 3">
<script>
(function() {
if ("loading" in HTMLImageElement.prototype) {
var lazyEls = document.querySelectorAll("[loading=lazy]");
lazyEls.forEach(function(lazyEl) {
lazyEl.setAttribute(
"src",
lazyEl.getAttribute("data-src")
);
});
} else {
// Dynamically include a lazy loading library of your choice
// Here including vanilla-lazyload
var script = document.createElement("script");
script.async = true;
script.src =
"https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.0.0/dist/lazyload.min.js";
window.lazyLoadOptions = {
elements_selector: "[loading=lazy]"
//eventually more options here
};
document.body.appendChild(script);
}
})();
</script>
你可以訪問下這個[線上Demo](https://www.andreaverlicchi.eu/lazyload/demos/native_lazyload_conditional_old.html),順便查看下網頁源碼。
不過這只是一個非常基礎的腳本,很多時候我們需要做一些響應式的圖片可能會添加其他的屬性或標簽(比如 `srcset` 和 `sizes` 屬性,甚者 `picture` 和 `source` 等標簽)。
3.1、A Little Third-Party Help
過去四年里,我一直在維護一個關于 Lazy-Loading 的開源項目,叫“vanilla-lazyload”,在 Addy Osmani 寫了關于原生 Lazy-Loading 的文章后,社區反饋說,我的腳本能否作為對應的 polyfill。
我在前面解釋過,你沒法為原生 Lazy-Loading 這個特性實現一個 polyfill。不過,我還是想到了一個解決方案,可以讓開發者們更容易地開始過度到原生 Lazy-Loading, 不需要寫任何 JS 代碼,我之前提到過的。
從 `vanilla-lazyload` 版本 12 開始,你可以僅僅設置 `use_native` 選項為 `true` 來開啟混合模式的 Lazy-Loading。這個腳本經過 gzip 壓縮后,只有 2.0 KB 大小了,而且已經可以使用了,在 [GitHub](https://github.com/verlok/lazyload),[npm](https://www.npmjs.com/package/vanilla-lazyload) 和 [jsDelivr](https://www.jsdelivr.com/package/npm/vanilla-lazyload) 上。
4、Demos
你現在可以通過下載 Chrome Canary (當然,你現在直接升級 Chrome 到 75 版本即可使用了)或 Microsoft Edge Insider 后,使用原生的 Lazy-Loading 特性。啟用 "Enable lazy image loading" 和 "Enable lazy frame loading" (例如打開 chrome://flags,搜索下 "lazy" 后,設置即可)。
4.1、NATIVE LAZY LOADING DEMO
為了分析下原生的 Lazy-Loading 怎么工作的,你可以玩下這個 Demo。在這個 Demo 中,沒有使用任何 JS 代碼。有且僅使用了原生的 Lazy-Loading。
* [測試原生 Lazy-Loading Demo](https://www.andreaverlicchi.eu/lazyload/demos/native_lazyload.html)
**驗證**:嘗試在不同的瀏覽器打開,看下有什么不同的表現。支持原生 Lazy-Loading 的瀏覽器,應該和之前的 Demo 表現一樣。在不支持的瀏覽器上,圖片將會在你滾動時就全部加載了。
需要指出的是 `vanilla-lazyload` 使用了 IntersectionObserver API,因為你需要在 IE 和老版本的 Safari 下 polyfill。不過如果沒有提供對應的 polyfill 的話也問題不大,因為在這個例子中,`vanilla-lazyload` 將一次性把所有圖片下載完。
**提示**: 建議閱讀一下 `vanilla-lazyload` 的 README 文件的 “[To Polyfill Or Not To Polyfill](https://github.com/verlok/lazyload/blob/master/README.md#to-polyfill-or-not-to-polyfill-intersectionobserver)” 章節。
5、Try Hybrid Lazy Loading In Your Website
既然原生的 Lazy-Loading 即將在一些瀏覽器上支持,為什么你不現在使用下混合模式的 Lazy-Loading 呢?我們開始吧:
5.1、HTML MARKUP
最簡單的圖片標記,一般就是兩個屬性:`src` 和 `alt`。
在屏幕內的圖片,你應該保留 `src` 屬性,同時加一個 `loading="eager"` 屬性。
<img src="important.jpg" loading="eager" alt="Important image">
在屏幕下的一些圖片,你應該將 `src` 屬性用 `data-src` 來替換,同時添加 `loading="lazy"` 屬性。
<img data-src="lazy.jpg" loading="lazy" alt="A lazy image">
如果你想使用一些響應式的圖片,保持這兩個 `srcset` 和 `sizes` 屬性即可,無需特殊處理。
<img alt="A lazy image"
loading="lazy"
data-src="lazy.jpg"
data-srcset="lazy_400.jpg 400w, lazy_800.jpg 800w"
data-sizes="100w">
如果你更喜歡用 `picture` 標簽,改一下 `srcset`,`sizes` 和 `src` 屬性,在 `source` 標簽上。
<picture>
<source
media="(min-width: 1200px)"
data-srcset="lazy_1200.jpg 1x, lazy_2400.jpg 2x">
<source
media="(min-width: 800px)"
data-srcset="lazy_800.jpg 1x, lazy_1600.jpg 2x">
<img alt="A lazy image"
loading="lazy"
data-src="lazy.jpg">
</picture>
`picture`標簽也能被用在需要選擇加載 WebP 格式的圖片上。
**提示**:如果你想知道更多 `vanilla-lazyload` 的用法,請讀一下 README 文件 "[Getting Started](https://github.com/verlok/lazyload/blob/master/README.md#-getting-started---html)" 的 HTML 部分。
5.2、JAVASCRIPT CODE
首先,你需要在你的網站里引入 `vanilla-lazyload`。
你可以加載它的 CDN 文件,如 jsDelivr 上的:
<script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.0.0/dist/lazyload.min.js"></script>
或者安裝一下對應的 npm 包:
npm install vanilla-lazyload@12
更多引入 `vanilla-lazy` 的方式,可以看 README 文件的 "[Getting Started](https://github.com/verlok/lazyload/blob/master/README.md#-getting-started---script)" 的 JS 部分。
然后,在你的網站或 Web 應用中,你需要寫如下的一些代碼:
var pageLazyLoad = new LazyLoad({
elements_selector: "[loading=lazy]",
use_native: true // ← enables hybrid lazy loading
});
**提示**:`vanilla-lazyload ` 支持很多其他自定義的設置,例如:增加滾動區域的距離,到了對應位置才開始加載元素;或者僅僅當元素在給定時間內在視窗內才加載等。更多的配置,可以看 README 文檔中的 [API 部分](https://github.com/verlok/lazyload/blob/master/README.md#-api)。
5.3、ALL TOGETHER, USING AN async SCRIPT
將這些全部放一起,并且使用 `async` (這是一個腳本加載和執行優先級的屬性,async 模式執行的優先級會較高,[更多說明可以看這篇文章](https://addyosmani.com/blog/script-priorities/))的方式來加載腳本,是性能最好的。比如參考入下代碼:
<!-- In-viewport images should be loaded normally, or eagerly -->
<img src="important.jpg" loading="eager" alt="Important image">
<!-- Let’s lazy-load the rest of these images -->
<img data-src="lazy1.jpg" loading="lazy" alt="Lazy image 1">
<img data-src="lazy2.jpg" loading="lazy" alt="Lazy image 2">
<img data-src="lazy3.jpg" loading="lazy" alt="Lazy image 3">
<!-- Set the options for the global instance of vanilla-lazyload -->
<script>
window.lazyLoadOptions = {
elements_selector: "[loading=lazy]",
use_native: true // ← enables hybrid lazy loading
};
</script>
<!-- Include vanilla lazyload 12 through an async script -->
<script async src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.0.0/dist/lazyload.min.js"></script>
就這樣了!就這些非常簡單和容易的步驟,你將可以在你的網站中啟用混合模式的 Lazy-Loading。
6、Important Best Practices
- 僅僅對圖片使用 Lazy-Loading 技術,你應該知道,是有可能會加載非視窗內的圖片的。建議主動去優先加載視窗內的東西,才能有效提升性能。如果你只是將網站上所有的圖片都設置為 Lazy-Loading,這樣甚至還會降低渲染性能。
- 使用 CSS 來預留一些你即將加載的圖片的空間。用這個方式,會向下推動其他需要展示的內容。如果你不這么做,很多圖片將在首屏立即觸發加載。如果你需要這個 CSS 技巧來處理這個事情,你可以在
vanilla-lazyload
的文檔中找到對應的技巧。詳情請點擊。
7、Pros And Cons
如下對三種 Lazy-Loading 模式做下優缺點對比:
優點 缺點
原生
不需要 JS;
不需要麻煩的設置,直接生效;
不需要使用 CSS 技巧來為圖片預留空間;
不兼容所有瀏覽器;
初始的有效負荷較高,因為需要為每一張圖片預加載 2KB.
JS 驅動
所有瀏覽器都兼容;
你能做任何個性化的 UI 技巧,比如模糊化或者延遲加載(一點點加載出來)等;
它依賴 JS
混合模式它支持是否開啟原生的 Lazy-Loading,如果瀏覽器支持的話;
所有瀏覽器都兼容;
你可以直接移除腳本依賴,只要原生的 Lazy-Loading 被廣泛支持了的話
它同樣依賴 JS
8、Wrapping Up
我非常地激動,原生的 Lazy-Loading 能到來。甚至希望所有的瀏覽器都能立即實現它。
同時,你可以選擇豐富 HTML 標記來進行漸進增強,且在支持原生 Lazy-Loading 的瀏覽器上能直接使用到該特性。或者,你可以先混合著使用 Lazy-Loading 技術,直到大量的瀏覽器都支持了該特性。
不妨試試。另外,別忘了給 GitHub 上的 `[vanilla-lazyload](https://github.com/verlok/lazyload)` 點贊和關注,同時可以評論讓我了解你們的想法。