定義
延遲加載也稱為惰性加載,即在長網頁中延遲加載圖像。用戶滾動到它們之前,視口外的圖像不會加載。這與圖像預加載相反,在長網頁上使用延遲加載將使網頁加載更快。在某些情況下,它還可以幫助減少服務器負載。
舉個例子來說明,當打開淘寶首頁的時候,只有在瀏覽器窗口里的圖片才會被加載,當你滾動首頁向下滑的時候,進入視口內的圖片才會被加載,而其它從未進入視口的圖像不會也不會加載。
那么延遲加載有什么好處
- 首先它能提升用戶的體驗,試想一下,如果打開頁面的時候就將頁面上所有的圖片全部獲取加載,如果圖片數量較大,對於用戶來說簡直就是災難,會出現卡頓現象,影響用戶體驗。
- 有選擇性地請求圖片,這樣能明顯減少了服務器的壓力和流量,也能夠減小瀏覽器的負擔。
那么下面就介紹延遲加載的三種實現方式:
第一種
首先將頁面上的圖片的 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>












<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>












<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>












<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>
- IntersectionObserver 傳入一個回調函數,當其觀察到元素集合出現時候,則會執行該函數。
- io.observe 即要觀察的元素,要一個個添加才可以。
- io 管理的是一個數組,當元素出現或消失的時候,數組添加或刪除該元素,並且執行該回調函數。