圖片懶加載介紹

什麽是懶加載?懶加載其實就是按需加載或者說是條件加載,是前端一種優化性能的方式。比如訪問一個頁面,裏面有很多圖片(也可以是視頻等其他資源)。如果這些圖片全部加載出來,不僅要在短時間內發出大量請求而且還要進行渲染,這是很消耗時間的。此外,在渲染過程中會阻塞瀏覽器繼續向下解析,影響用戶體驗。所以,最佳的模式是訪問一個頁面的時候當圖片出現在可視區域內的時候(或者距離可視區域一定距離的時候)再去加載而不是一開始就加載全部圖片。當需要加載的時候再發出圖片請求,避免網頁初始化時請求擁堵以及過多渲染阻塞線程。

圖片懶加載原理

img 標簽有一個屬性是 src,用來表示圖片的地址,當這個屬性的值不為空時,瀏覽器就會根據這個屬性值發出請求;如果這個屬性值為空,則不會發出請求。根據這個原理,將圖片地址賦值給 img 標簽的 data-src 屬性,而 src 屬性值設為空或者設為一個默認圖片的路徑。

獲取 img 標簽到瀏覽器頂部的距離,當這個距離等於某個值或在某個範圍內時(開發者自己決定,一般以瀏覽器窗口的可視區域高度為參考標準。),將 data-src 的屬性值賦值給 src 屬性。

圖片懶加載實現

getBoundingClientRect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>圖片懶加載</title>

<style>
* {
margin: 0;
}

li {
width: 50%;
height: 500px;
}
</style>
</head>

<body>
<ul>
<li>
<img src="" data-src="1.jpg" />
</li>

<li>
<img src="" data-src="2.jpg" />
</li>

<li>
<img src="" data-src="3.jpg" />
</li>

<li>
<img src="" data-src="4.jpg" />
</li>

<li>
<img src="" data-src="5.jpg" />
</li>

<li>
<img src="" data-src="6.jpg" />
</li>

<li>
<img src="" data-src="7.jpg" />
</li>

<li>
<img src="" data-src="8.jpg" />
</li>

<li>
<img src="" data-src="9.jpg" />
</li>
</ul>
</body>

<script>
let imgArr = document.querySelectorAll('img')
imgArr = Array.from(imgArr) // 將類數組對象轉化為數組

const imgLazyload = () => {
const lazyDistance = 20 // 圖片提前加載的距離

for (let index = 0; index < imgArr.length; index++) {
const imgDOM = imgArr[index]

// 獲取圖片上邊框到瀏覽器可視區域頂部的距離
const imgDOMtop = imgDOM.getBoundingClientRect().top

// 獲取瀏覽器可視區域的高度
const clientHeight =
document.documentElement.clientHeight || window.innerHeight

if (imgDOMtop - clientHeight <= lazyDistance) {
imgDOM.src = imgDOM.dataset.src

// 已經加載的圖片從數組中移除從而避免重復操作
imgArr.splice(index, 1)

/*
數組中移除一個元素後,被移除元素後面的元素向前移動一位。
如果「index」不減少一個單位,則會跳過原本在被移除元素後面的那個元素。
*/
index -= 1
}
}
}

// 當頁面滾動的時候觸發圖片懶加載邏輯
window.addEventListener('scroll', () => {
imgLazyload()
})

// 頁面加載完成後首屏加載一次
window.addEventListener('load', () => {
imgLazyload()
})
</script>
</html>

(以上代碼粘貼到 html 文件中即可運行體驗效果)

IntersectionObserver

上述方法是通過綁定「scroll」事件實現的,當頁面滾動時,判斷被觀察元素上邊框與視口頂部的距離從而判斷被觀察元素是否在視口內。但是 scroll 事件觸發過於頻繁,很影響頁面的流暢性,甚至會出現卡頓現象。幸運的是,有一個新的接口 IntersectionObserver,既可以觀察元素是否在視口內又完美地解決了上述問題。

1
2
3
4
5
const observer = new IntersectionObserver(callback, option)

observer.observe(document.querySelector('#domId'))
observer.unobserve(document.querySelector('#domId'))
observer.disconnect()

IntersectionObserver 是瀏覽器提供的原生構造函數,接受兩個參數,第一個參數是回調函數,另外一個是可選配置參數。該構造函數的返回值是一個觀察器實例。observe 用來指定被觀察的元素,如果要觀察多個元素,需要多次調用這個方法。只要被觀察元素的可見性發生變化,就會執行觀察器的回調函數(回調函數會在被觀察元素剛剛進入視口或者完全離開視口時觸發)。unobserve 用來取消對元素的觀察,用法與 observe 相同。disconnect 用來關閉觀察器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>圖片懶加載</title>

<style>
* {
margin: 0;
}

li img {
width: 50%;
height: 200px;
margin-bottom: 2000px;
}
</style>
</head>

<body>
<ul>
<li>
<img src="" data-src="1.jpg" />
</li>

<li>
<img src="" data-src="2.jpg" />
</li>

<li>
<img src="" data-src="3.jpg" />
</li>

<li>
<img src="" data-src="4.jpg" />
</li>

<li>
<img src="" data-src="5.jpg" />
</li>

<li>
<img src="" data-src="6.jpg" />
</li>

<li>
<img src="" data-src="7.jpg" />
</li>

<li>
<img src="" data-src="8.jpg" />
</li>

<li>
<img src="" data-src="9.jpg" />
</li>
</ul>
</body>

<script>
let imgArr = document.querySelectorAll('img')
imgArr = Array.from(imgArr) // 將類數組對象轉化為數組

// 初始化一個實例
const observer = new IntersectionObserver((changes) => {
// changes是一個數組,被觀察元素的可見性若是發生變化,該元素的信息就會存入數組中。
for (let index = 0; index < changes.length; index++) {
const changer = changes[index]

// console.log(changer.time); // 被觀察元素可見性發生變化時的時間,是一個以毫秒為單位的時間戳。
// console.log(changer.target); // 可見性發生變化的目標元素
// console.log(changer.rootBounds); // 根元素矩形區域的信息,默認根元素是瀏覽器視口。
// console.log(changer.boundingClientRect); // 目標元素矩形區域的信息
// console.log(changer.intersectionRect); // 目標元素與根元素(默認根元素是瀏覽器視口)交叉區域的信息
// console.log(changer.intersectionRatio); // 目標元素的可見比例,即intersectionRect占boundingClientRect的比例。

if (changer.intersectionRatio > 0) {
const targetDOM = changer.target

targetDOM.src = targetDOM.dataset.src

// 對已經出現在視口的元素取消觀察
observer.unobserve(targetDOM)
}
}
})

for (let index = 0; index < imgArr.length; index++) {
const imgDOM = imgArr[index]

observer.observe(imgDOM)
}
</script>
</html>

(以上代碼粘貼到 html 文件中即可運行體驗效果)

IntersectionObserver 的第二個參數是一個配置對象。其可以有以下屬性:

  • threshold:默認值為 [0],表示 intersectionRatio == 0 時回調函數會被觸發。當然,開發人員也可以設置為 [0, 0.5, 1],表示 intersectionRatio 等於 0、0.5、1 時回調函數會被觸發。

  • root:默認情況下,被觀察元素的可見性變化是相對於瀏覽器視口的,既以瀏覽器視口為參考系的。該屬性的作用就是修改被觀察元素的參考視口。

  • rootMargin:這個屬性用來給 root 元素設置 margin 值,使用方法與 CSS 中的 margin 屬性一樣。運行上面的兩個案例會發現,在第一個案例中,圖片標簽將要出現在視口內時就開始加載圖片,另一個案例恰恰相反,圖片標簽出現在視口後才開始加載圖片(被觀察元素只有與 root 元素有交叉(交集)時才會觸發回調函數),並未達到預加載效果。給 root 元素設置 rootMargin 屬性後,被觀察元素只要與 root 元素外邊距有交叉(交集)時就會觸發回調函數,達到了未進入視口前就開始加載的效果。

1
2
3
4
5
const observer = new IntersectionObserver(callback, {
threshold: [0, 0.5, 1],
root: document.querySelector('#domId'),
rootMargin: '0 0 20px 0',
})

註意:如果被觀察元素不是 root 元素的子節點,即使被觀察元素出現在瀏覽器視口內,也不會觸發回調函數。

IntersectionObserver 是一個異步接口,其回調函數只有在瀏覽器線程空閑的時候才會執行,如果瀏覽器當前的任務隊列中有待處理的任務,其回調函數是不會被執行的。

參考鏈接:https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect