1. 程式人生 > >延遲載入(Lazyload)三種實現方式

延遲載入(Lazyload)三種實現方式

定義:延遲載入也稱為惰性載入,即在長網頁中延遲載入影象。使用者滾動到它們之前,視口外的影象不會載入。這與影象預載入相反,在長網頁上使用延遲載入將使網頁載入更快。在某些情況下,它還可以幫助減少伺服器負載。

舉個例子來說明,當開啟淘寶首頁的時候,只有在瀏覽器窗口裡的圖片才會被載入,當你滾動首頁向下滑的時候,進入視口內的圖片才會被載入,而其它從未進入視口的影象不會也不會載入。

那麼延遲載入有什麼好處:

  1. 首先它能提升使用者的體驗,試想一下,如果開啟頁面的時候就將頁面上所有的圖片全部獲取載入,如果圖片數量較大,對於使用者來說簡直就是災難,會出現卡頓現象,影響使用者體驗。
  2. 有選擇性地請求圖片,這樣能明顯減少了伺服器的壓力和流量,也能夠減小瀏覽器的負擔。

那麼下面就介紹延遲載入的三種實現方式:

第一種:

首先將頁面上的圖片的 src 屬性設為 loading.gif,而圖片的真實路徑則設定在 data-src 屬性中,頁面滾動的時候計算圖片的位置與滾動的位置,當圖片出現在瀏覽器視口內時,將圖片的 src 屬性設定為 data-src 的值,這樣,就可以實現延遲載入。

下面是具體的實現程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Lazyload 1</title
> <style> img { display: block; margin-bottom: 50px; height: 200px; } </style> </head> <body> <img src="images/loading.gif" data-src="images/1.png"> <img src="images/loading.gif" data-src="images/2.png"> <img src="images/loading.gif"
data-src="images/3.png"> <img src="images/loading.gif" data-src="images/4.png"> <img src="images/loading.gif" data-src="images/5.png"> <img src="images/loading.gif" data-src="images/6.png"> <img src="images/loading.gif" data-src="images/7.png"> <img src="images/loading.gif" data-src="images/8.png"> <img src="images/loading.gif" data-src="images/9.png"> <img src="images/loading.gif" data-src="images/10.png"> <img src="images/loading.gif" data-src="images/11.png"> <img src="images/loading.gif" data-src="images/12.png"> <script> function lazyload() { var images = document.getElementsByTagName('img'); var len = images.length; var n = 0; //儲存圖片載入到的位置,避免每次都從第一張圖片開始遍歷 return function() { var seeHeight = document.documentElement.clientHeight; var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; for(var i = n; i < len; i++) { if(images[i].offsetTop < seeHeight + scrollTop) { if(images[i].getAttribute('src') === 'images/loading.gif') { images[i].src = images[i].getAttribute('data-src'); } n = n + 1; } } } } var loadImages = lazyload(); loadImages(); //初始化首頁的頁面圖片 window.addEventListener('scroll', loadImages, false); </script> </body> </html>

比較 image 的 offsetTop 與 seeHeight + scrollTop 的大小,當小於時則說明圖片已經出現過在視口中,這時候繼續判斷圖片是否已經替換過,如果沒有替換過,則進行替換。

實現的效果:不斷滑動頁面時,圖片延遲載入

你可以拷貝我的程式碼去進行實驗,但是請確保 HTML 同目錄下有 images 目錄並且含有 1~12.png 和 loading.gif。

需要提及的是變數 n 是用來儲存已經載入的圖片數量,避免每次都從第一張圖片開始遍歷,提升效能。上面的程式碼用到了 JS 閉包的知識,如果你不太熟悉的話,可以自行百度一下。

第二種:

上面的程式碼是沒什麼問題,但是效能偏差。如果直接將函式繫結在 scroll 事件上,當頁面滾動時,函式會被高頻觸發,這非常影響瀏覽器的效能。我粗略地估計一下,當簡單地滾動一下頁面,函式至少觸發了十來次,這顯然是十分沒必要的。
所以在做事件繫結的時候,可以對 lazyload 函式進行函式節流(throttle)與函式去抖(debounce)處理。
這裡我並不再另外介紹這兩種方案,如果你想了解的話可以閱讀:JS魔法堂:函式節流(throttle)與函式去抖(debounce) - ^_^肥仔John - 部落格園

簡單說來:

  • Debounce:一部電梯停在某一個樓層,當有一個人進來後,20秒後自動關門,這20秒的等待期間,又一個人按了電梯進來,這20秒又重新計算,直到電梯關門那一刻才算是響應了事件。
  • Throttle:好比一臺自動的飲料機,按拿鐵按鈕,在出飲料的過程中,不管按多少這個按鈕,都不會連續出飲料,中間按鈕的響應會被忽略,必須要等這一杯的容量全部出完之後,再按拿鐵按鈕才會出下一杯。

下面就是經過 throttle 處理後的程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Lazyload 2</title>
    <style>
	img {
	    display: block;
	    margin-bottom: 50px;
	    height: 200px;
	}
    </style>
</head>
<body>
    <img src="images/loading.gif" data-src="images/1.png">
    <img src="images/loading.gif" data-src="images/2.png">
    <img src="images/loading.gif" data-src="images/3.png">
    <img src="images/loading.gif" data-src="images/4.png">
    <img src="images/loading.gif" data-src="images/5.png">
    <img src="images/loading.gif" data-src="images/6.png">
    <img src="images/loading.gif" data-src="images/7.png">
    <img src="images/loading.gif" data-src="images/8.png">
    <img src="images/loading.gif" data-src="images/9.png">
    <img src="images/loading.gif" data-src="images/10.png">
    <img src="images/loading.gif" data-src="images/11.png">
    <img src="images/loading.gif" data-src="images/12.png">
    <script>
	function throttle(fn, delay, atleast) {
	    var timeout = null,
		startTime = new Date();
	    return function() {
		var curTime = new Date();
		clearTimeout(timeout);
		if(curTime - startTime >= atleast) {
		    fn();
		    startTime = curTime;
		}else {
		    timeout = setTimeout(fn, delay);
		}
	    }
	}
	function lazyload() {
	    var images = document.getElementsByTagName('img');
	    var len    = images.length;
	    var n      = 0;      //儲存圖片載入到的位置,避免每次都從第一張圖片開始遍歷		
	    return function() {
		var seeHeight = document.documentElement.clientHeight;
		var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
		for(var i = n; i < len; i++) {
		    if(images[i].offsetTop < seeHeight + scrollTop) {
		        if(images[i].getAttribute('src') === 'images/loading.gif') {
			     images[i].src = images[i].getAttribute('data-src');
		        }
			n = n + 1;
		     }
		}
	    }
	}
	var loadImages = lazyload();
	loadImages();          //初始化首頁的頁面圖片
	window.addEventListener('scroll', throttle(loadImages, 500, 1000), false);
    </script>
</body>
</html>

設定了 500ms 的延遲,和 1000ms 的間隔,當超過 1000ms 未觸發該函式,則立即執行該函式,不然則延遲 500ms 執行該函式。

實現效果:可以看出有一定的延遲。

第三種: 使用 IntersectionObserver API

目前有一個新的 IntersectionObserver API,可以自動"觀察"元素是否可見,Chrome 51+ 已經支援。

這裡不過多介紹 IntersectionObserver API 的詳細使用,感興趣可以另外閱讀下面的文章:

實現程式碼:簡潔,但是瀏覽器尚未全部實現。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Lazyload 3</title>
	<style>
	    img {
		display: block;
		margin-bottom: 50px;
		width: 800px;
	    }
    </style>
</head>
<body>
    <img src="images/loading.gif" data-src="images/1.png">
    <img src="images/loading.gif" data-src="images/2.png">
    <img src="images/loading.gif" data-src="images/3.png">
    <img src="images/loading.gif" data-src="images/4.png">
    <img src="images/loading.gif" data-src="images/5.png">
    <img src="images/loading.gif" data-src="images/6.png">
    <img src="images/loading.gif" data-src="images/7.png">
    <img src="images/loading.gif" data-src="images/8.png">
    <img src="images/loading.gif" data-src="images/9.png">
    <img src="images/loading.gif" data-src="images/10.png">
    <img src="images/loading.gif" data-src="images/11.png">
    <img src="images/loading.gif" data-src="images/12.png">
    <script>
	function query(selector) {
	    return Array.from(document.querySelectorAll(selector));
	}
	var io = new IntersectionObserver(function(items) {
	    items.forEach(function(item) {
		var target = item.target;
		if(target.getAttribute('src') == 'images/loading.gif') {
		    target.src = target.getAttribute('data-src');
		}
	    })
	});
	query('img').forEach(function(item) {
	    io.observe(item);
	});
    </script>
</body>
</html>
  1. IntersectionObserver 傳入一個回撥函式,當其觀察到元素集合出現時候,則會執行該函式。
  2. io.observe 即要觀察的元素,要一個個新增才可以。
  3. io 管理的是一個數組,當元素出現或消失的時候,陣列新增或刪除該元素,並且執行該回調函式。

實現效果: