圖片懶加載從簡單到復雜
圖片懶加載是一個很重要的前端性能優化手段。這篇文章將從懶加載的最簡單場景開始介紹,逐步增加復雜度,希望能講清楚常見的圖片懶加載場景及在該場景下對應的解決辦法,也希望對你有所幫助。
為什么要做圖片的懶加載
假設在用戶訪問某個頁面時就加載這個頁面全部的圖片(即使這些圖片并不處在用戶的當前的視窗中),在弱網環境或者網速較慢的環境下,這些“冗余”圖片的下載會占用用戶本來就非常有限的帶寬,傷害用戶體驗(比如影響其他資源的下載)。所以對于網站的圖片,理想的做法是懶加載(按需加載)。
圖片懶加載的原理
在瀏覽器內部對于各種資源有著一套自己的優先級定義,瀏覽器會優先加載優先級高的資源。
如果我們不去進行圖片的懶加載,默認情況下,資源的priority如下。
這些優先級標記為high的圖片會占用其他資源的下載帶寬,可能會造成某些比較關鍵的資源(比如xhr call)加載緩慢,拖慢頁面速度。
圖片懶加載的簡單實現
圖片懶加載的思路一般時當頁面加載時加載一個尺寸很小的占位圖片(1kb以下),然后再通過js選擇性的去加載真正的圖片。
一個最簡單的的實現如下:
- <!-- index.html --> <img src="placeholder.jpg" src="real_image.jpt" />
- // index.css
- img[src] {
- filter: blur(0.2em);
- }
- img {
- filter: blur(0em);
- transition: filter 0.5s;
- }
- (function lazyLoad(){
- const imageToLazy = document.querySelectorAll('img[src]');
- const loadImage = function (image) {
- image.setAttribute('src', image.getAttribute('src'));
- image.addEventListener('load', function() {
- image.removeAttribute("src");
- })
- }
- imageToLazy.forEach(function(image){
- loadImage(image);
- })
- })()
通過懶加載之后,資源優先級如下。
圖片懶加載的進階實現–滾動加載
上面的方案并不完美,對于用戶來說,不在視窗中的圖片可能根本不是用戶當前關心的圖片,所以我們可以讓這些圖片出現在用戶視窗中再進行加載。
運用Intersection Observer 我們可以做到當圖片滾動到視窗后再加載該圖片。
- (function lazyLoad(){
- const imageToLazy = document.querySelectorAll('img[src]');
- const loadImage = function (image) {
- image.setAttribute('src', image.getAttribute('src'));
- image.addEventListener('load', function() {
- image.removeAttribute("src");
- })
- }
- const intersectionObserver = new IntersectionObserver(function(items, observer) {
- items.forEach(function(item) {
- if(item.isIntersecting) {
- loadImage(item.target);
- observer.unobserve(item.target);
- }
- });
- });
- imageToLazy.forEach(function(image){
- intersectionObserver.observe(image);
- })
- })()
上面的這些demo都在https://github.com/hateonion/lazy-load 這個repo里面。
如何選擇合適的Placeholder圖片
在上面的demo中我們使用了placeholder圖片,實際上,圖片所占的位置是否確定對于我們選擇placeholder圖片有著很大的影響。
圖片尺寸已知
圖片尺寸已知出現的場景一般是博文的題圖或者網站中一些固定尺寸的thumbnail,這些圖的尺寸一般固定且一般不會發生改變。對于這種場景,我們可以加載對應尺寸的placeholder圖片(如上一節的demo)。我們可以自己裁剪對應尺寸的的placeholder圖片或者使用類似http://placeholder.com/ 這樣的服務來獲取placeholder圖片。
圖片尺寸未知
圖片尺寸未知的情況下一般我們需要生成對應的thumbnail然后去加載我們生成的thumbnail去做placeholder。為了生成這些thumbnail你可以調用imagemagick或者調用一些在線的圖片分割服務(比如七牛)
懶加載防止布局抖動
在圖片懶加載時,由于圖片的尺寸不定,瀏覽器難以計算需要給圖片預留出的位置。所以當圖片加載完成后會出現網頁布局的抖動。
(image from From http://davidecalignano.it/lazy-loading-with-responsive-images-and-unknown-height/)
即使我們選擇的placeholder很小,可以在毫秒級別完成下載,用戶可能意識不到布局的抖動。但是在一些性能比較差的設備上,這種布局的抖動還是會一定程度上影響用戶的體驗。為了完全避免布局閃動,我們可以采用aspect ratio boxes 的技術來制作一個占位用的元素。
- <div class="lazy-load__container feature">
- <img src="placeholder.jpg" src="real.jpg" />
- </div>
- .lazy-load__container{
- position: relative;
- display: block;
- height: 0;
- }
- .lazy-load__container.feature {
- // feature image 的高寬比設置成42.8%
- // 對于其他圖片 比如 post圖片,高寬比可能會不同,可以使用其他css class去設置
- padding-bottom: 42.8%;
- }
- .lazy-load__container img {
- position: absolute;
- top:0;
- left:0;
- height: 100%;
- width: 100%;
- }
結果
上面這個實現的原理其實很簡單,由于 padding-bottom (或者 padding-top)聲明為百分比時是根據元素生成的box的 width 去計算百分比的,所以我們通過padding-bottom去聲明一個對應高寬比的container。而這個container的具體尺寸會由尺寸確定的外層元素確定,但是高寬比始終保持一致。
而圖片的尺寸設置成100%container的尺寸保證圖片始終和container的尺寸保持一致。
需要注意的是上面這個方法并不能適配圖片比例不一致的網站(比如本站),不過好在,為了用戶體驗,現在絕大多數網站的圖片比例都有明確的要求,絕大多數情況下我們只適配保證網站常用的的幾種圖片寬高比例即可。
像Medium一樣懶加載圖片
Medium的懶加載圖片的體驗相信去 Medium 讀過文章的同學都體驗過了,可以說是非常的流暢。而其背后的技術其實也就是我們上面講到的幾種技術的組合。
- 使用 aspect ratio box 創建占位元素。
- 在html解析時只加載一個小尺寸的圖片,并且添加blur效果。
- 最后使用js選擇性的加載真實圖片。
Demo 如下 codePen by José M. Pérez
總結
- 懶加載用戶當前視窗中的圖片可以提升頁面的加載性能。
- 懶加載的思路是在html解析時先加載一個placeholder圖片,最后再用js選擇性的加載真實圖片。
- 如果需要滾動加載可以使用 Intersection Observer 。
- 對于固定尺寸和不定尺寸的圖片,我們可以選擇不同的服務去或者placeholder圖片。
- 對于圖片尺寸不確定引起的布局抖動問題我們可以使用 aspect ratio box 來解決。