[Javascript] 圖片、背景圖隨著使用者的捲軸下拉才延遲載入

Image and background-image Lazy Load using Javascript

前言

現在Landing Page正當道,單一網頁都長長的,圖片也用得多

對用戶而言,他一進入網站,在用戶的可視範圍外,明明他就還沒看到圖片,瀏覽器就在背景作業發出Request去下載

在瀏覽器發送 Request 的時候,瀏覽器會限制同一時間發出的 Request 數量,如果當網站的資源很多時,瀏覽器會依照限制數量分批下載

這樣就會造成後續的圖片、.js、.css…等等其他靜態資源阻塞佇列排隊中,形成網頁前端的效能瓶頸

其實只要在用戶操作瀏覽器捲軸往下捲動快要到達圖片位置時,瀏覽器才去下載圖片,這樣就能避免同一時間瀏覽器大量發送 Request 增加網站&用戶的等待時間

Image Lazy Load技術目的就是為了避免瀏覽器同一時間發出太多 Request 的前端解決方案,如此才能加快網頁載入速度

上圖為某網站使用Chrome開發者工具的觀察結果,稍微解釋一下各數據

DOMContentLoaded:和網頁HTML複雜度有關,愈複雜載入時間愈久

Load:網頁多久載入完成(包含HTML文件、.js、.css、img等等靜態資源),使用者多久可以看到網頁

Finish:通常和Load的數據差不多(但會多一些些),但如果網頁載入(Load)後,有發出Ajax或再度下載資源,Finish時間會再度增加,但DOMContentLoaded、Load的秒數不變

通常花費時間由小至大:DOMContentLoaded < Load < Finish

其他數據說明請見Goolge官網:View the timing breakdown of a request 

 

本文選用的Javascript套件是Lazysizes plugin:https://github.com/aFarkas/lazysizes

其優點支援CSS背景圖片也能Lazy Load,而且引用它的JS Library後,只要再修改一下<img>的HTML語法即可,不必額外多寫Javascript代碼,使用方法簡單,也沒相依jQuery

Youtube上有老外教學影片:How to lazyload images by Alex Carpenter

實作

先從<img> 如何Lazy Load開始講起

第一步,先到Lazysizes plugin:https://github.com/aFarkas/lazysizes 下載「lazysizes.min.js script」,並加入自己的工作專案上

把lazysizes.min..js引入自己的網頁上,接著修改想要Lazy Load(延遲載入)的圖片HTML代碼

src改成data-src,瀏覽器一解析到此圖片沒有src屬性便不會發出Request,data-src要給JS套件抓圖片網址,套件會自動產生圖片的src屬性並給予data-src的網址

class加上lazyload關鍵字,套件才知道哪一張圖要有Lazy Load效果

以下是完整代碼

<!DOCTYPE html>
<html>
<head> 
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" /> 
    <!--icon setting -->
    <link rel="icon" type="image/png" href="Content/img/favorite.ico" /> 
    <title>Image Lazy Load</title>
    <style type="text/css">
        html, body {
            height: 100%;
        } 
        img {
            display: block; /*圖片自動換行*/
            height: 400px;
            margin-bottom: 600px; /*每張圖的間距*/
        }
    </style>
</head>
<body> 

    <!--通常第一張圖片,用戶一進入網站就必須馬上看得到,不需要Lazy Load-->
    <img src="Content/img/1.jpg" /> 
    <!--第二張圖之後(用戶可視範圍外)才需要Lazy Load-->
    <img data-src="Content/img/2.jpg" class="lazyload" />
    <img data-src="Content/img/3.jpg" class="lazyload" />
    <img data-src="Content/img/4.jpg" class="lazyload" />
    <img data-src="Content/img/5.jpg" class="lazyload" />
    <img data-src="Content/img/6.jpg" class="lazyload" />
    <img data-src="Content/img/7.jpg" class="lazyload" />
    <img data-src="Content/img/8.jpg" class="lazyload" />

    <!--引用lazysizes-->
    <script src="Content/js/lazysizes.min.js"></script>

</body>
</html>

程式執行結果:

原本這樣↓

圖片Lazy Load延遲載入後變成這樣↓

本文為了展示,特地選用好幾MB的圖片來測試效能,實際上超過150KB圖片記得要先壓縮後再放入工作專案裡

否則大圖檔就算使用Lazy Load技術可能會讓用戶看到以下被截斷的情形

壓縮圖片的程式碼請見我另一篇文章:[C#.Net] 壓縮圖片,指定JPG的壓縮品質

另外提醒一下,實作Lazy Load Image的網頁,如果用戶瀏覽器Ctrl+S儲存完整網頁到他的電腦中,用戶下載的圖片是有可能缺圖的(因為當下網頁沒載入全部圖片)

用戶必須把瀏覽器捲軸捲到最下方,待網頁載入全部圖片後再按Ctrl+S→「儲存完整網頁」才能把網頁的全部圖片下載至他自己的電腦

官網有提供CSS API 讓圖片載入時有動畫淡入淡出效果,不過這套件貌似並非使用者捲動到圖片位置時才下載圖片,而會先行下載下一張圖

這樣做動畫效果就沒意義,因為使用者也看不到動畫,所以乾脆作罷不介紹XD

20191120追記

最近工作上Lazy Load Image效果要和 jQuery bxSlider圖片輪播套件整合,試了好久,參考以下這篇,總算成功

How do I integrate lazyload & bxslider together?

只不過這套件要自己寫背景圖的Lazy Load


//add simple support for background images lazyload:
document.addEventListener('lazybeforeunveil', function (e) {
    var bg = e.target.getAttribute('data-bg');
    if (bg) {
        e.target.style.backgroundImage =  bg;
    }
});

HTML代碼↓

 
<section class="lazyload" data-bg="url('../images/idx_advisory.jpg')">
</section>

 

 

 

背景圖片的Lazy Load延遲載入

出自 Youtube影片:How to lazyload background images 

同個作者套件,去官網:lazysizes bgset extension - responsive background images

把ls.bgset.min.js加入自己的工作專案並引用進網頁中,接著修改背景圖的容器HTML代碼

↑加入class關鍵字"lazyload"、background-image移除改成data-bgset,使用邏輯和<img>差不多

完整程式碼↓

<!DOCTYPE html>
<html>
<head> 
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" /> 
    <!--icon setting -->
    <link rel="icon" type="image/png" href="Content/img/favorite.ico" /> 
    <title>Background Image Lazy Load</title>
    <style type="text/css">
        html, body {
            height: 100%;
        } 
        div {
            background-size:cover;
            background-position:center center;
            height: 400px;
            margin-bottom: 600px; /*每個div的間距*/
        }
    </style>
</head>
<body>

    <!--通常第一張圖片,用戶一進入網站就必須馬上看得到,不需要Lazy Load-->
    <div style="background-image:url(Content/img/1.jpg)"></div>

    <!--第二張圖之後(用戶可視範圍外)才需要Lazy Load-->
    <div class="lazyload" data-bgset="Content/img/2.jpg"></div>
    <div class="lazyload" data-bgset="Content/img/3.jpg"></div>
    <div class="lazyload" data-bgset="Content/img/4.jpg"></div>
    <div class="lazyload" data-bgset="Content/img/5.jpg"></div>
    <div class="lazyload" data-bgset="Content/img/6.jpg"></div>
    <div class="lazyload" data-bgset="Content/img/7.jpg"></div> 
    <div class="lazyload" data-bgset="Content/img/8.jpg"></div>
     

    <!--引用順序我試沒什麼差別,但還是照官網寫吧-->
    <script src="Content/js/ls.bgset.min.js"></script>
    <!--引用lazysizes-->
    <script src="Content/js/lazysizes.min.js"></script>

</body>
</html>

注意:這個背景圖Lazy Load套件會同一張圖送出Request下載兩次,瀏覽器寬度大小拉著變動(雖然很少使用者這樣做),圖片也會再度多重下載

不知道是不是因為這樣效能不太好的原因,作者建議棄用此套件(ls.bgset.js),所以使用上請斟酌一下,如果你的網頁中有太多背景圖,請不要使用此套件,否則適得其反

至於背景圖Lazy Load,如果我有找到更好的套件,日後有空再補上

2019-10-20 補充

此作者的背景圖片Lazy Load效能比較好:https://github.com/verlok/lazyload (vanilla-lazyload)

Youtube教學影片:Lazy Loading Images by iEatWebsites

使用方法如下,完整程式碼

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!--icon setting -->
    <link rel="icon" type="image/png" href="Content/img/favorite.ico" />
    <title>Background Image Lazy Load</title>
    <style type="text/css">
        html, body {
            height: 100%;
        }

        div {
            background-size: cover;
            background-position: center center;
            height: 400px;
            margin-bottom: 600px; /*每個div的間距*/
        } 
    </style>
</head>
<body>

    <!--通常第一張圖片,用戶一進入網站就必須馬上看得到,不需要Lazy Load-->
    <div style="background-image:url(Content/img/1.jpg)"></div>

    <!--第二張圖之後(用戶可視範圍外)才需要Lazy Load-->
    <div class="lazy" data-bg="url(Content/img/2.jpg)"></div>
    <div class="lazy" data-bg="url(Content/img/3.jpg)"></div>
    <div class="lazy" data-bg="url(Content/img/4.jpg)"></div>
    <div class="lazy" data-bg="url(Content/img/5.jpg)"></div>
    <div class="lazy" data-bg="url(Content/img/6.jpg)"></div>
    <div class="lazy" data-bg="url(Content/img/7.jpg)"></div>
    <div class="lazy" data-bg="url(Content/img/8.jpg)"></div>


    <script src="https://cdn.jsdelivr.net/npm/intersection-observer@0.7.0/intersection-observer.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.1.0/dist/lazyload.min.js"></script>
    <!--↓多了一個JS步驟-->
    <script type="text/javascript">
        var lazyLoadInstance = new LazyLoad({
            elements_selector: ".lazy" //指定哪個element要Lazy Load
        });
    </script>
</body>
</html>

他的圖片Lazy Load的完整程式碼(含轉場動畫淡入效果)↓

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!--icon setting -->
    <link rel="icon" type="image/png" href="Content/img/favorite.ico" />
    <title>Image Lazy Load</title>
    <style type="text/css">
        html, body {
            height: 100%;
        } 
        img {
            display: block; /*圖片自動換行*/
            border: 0;
            height: 500px;
            margin-bottom: 200px; /*圖片間距*/  
        } 
    </style> 
    <!--Lazy Load Image的fadeIn轉場效果CSS-->
    <style>
           img.lazy {
             opacity: 0; /*Lazy Load的圖片預設透明*/
            } 
            img.loaded, 
            img.error {
				transition: opacity 1s;
			}
            img.initial,
			img.loaded,
			img.error {
				opacity: 1;
			} 
    </style>
</head>
<body>

    <!--通常第一張圖片,用戶一進入網站就必須馬上看得到,不需要Lazy Load-->
    <img src="Content/img/1.jpg" />

    <!--第二張圖之後(用戶可視範圍外)才需要Lazy Load-->
    <img data-src="Content/img/2.jpg" class="lazy" />
    <img data-src="Content/img/3.jpg" class="lazy" />
    <img data-src="Content/img/4.jpg" class="lazy" />
    <img data-src="Content/img/5.jpg" class="lazy" />
    <img data-src="Content/img/6.jpg" class="lazy" />
    <img data-src="Content/img/7.jpg" class="lazy" />
    <img data-src="Content/img/8.jpg" class="lazy" />


    <script src="https://cdn.jsdelivr.net/npm/intersection-observer@0.7.0/intersection-observer.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vanilla-lazyload@12.1.0/dist/lazyload.min.js"></script>
    <!--↓多了一個JS步驟-->
    <script type="text/javascript">
        var lazyLoadInstance = new LazyLoad({
            elements_selector: ".lazy", //指定哪個element要Lazy Load
            threshold:0 //數字太大就會當捲軸距離下一張圖片還很遠時,下一張圖就預先被下載,使用者看不到動畫fadeIn效果
        });
    </script>

</body>
</html>

↑此JS套件也支援 HTML5 <video>影片LazyLoad,不過我沒看到 poster 屬性的 lazyLoad 方式

 

結語

雖然Chrome在76版之後可以針對<img>有加上loading="lazy"屬性的圖片做延遲載入:

Youtube:Lazy Loading Images without JavaScript!

Native lazy-loading for the web | web.dev

但為了瀏覽器兼容性,而且我也不確定這樣能否通過工具的效能檢測(畢竟Chrome才剛支援,還很新的東西)

所以還是自己找個JS Plugin來使用吧~