原文地址:圖片懶加載踩坑javascript
對網頁加載速度影響最大的就是圖片,一張普通的圖片可能會有好幾 M 的大小,當圖片不少時,網頁的加載速度變得很緩慢。html
爲了優化網頁性能以及用戶體驗,咱們對圖片進行懶加載
。vue
懶加載是一種對網頁性能優化的方式,它的原理是優先加載在可視區域內的圖片,而不一次性加載因此圖片。當瀏覽器滾動,圖片進入可視區時再去加載圖片。經過設置圖片的 src
屬性來讓瀏覽器發起圖片的請求。當這個屬性爲空或者沒有時,就不會發送請求。java
此文所涉及的懶加載皆是在垂直方向上的滾動加載,橫向滾動暫不考慮。git
懶加載的實現主要是判斷當前圖片是否到了可視區域這一核心邏輯。咱們先來整理一下實現思路:github
img dom
。scroll
事件,對其進行事件監聽。<div class="container">
<div class="img-area">
<img id="first" data-src="./img/ceng.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/data.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/huaji.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/liqi1.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/liqi2.png" alt="">
</div>
<div class="img-area">
<img data-src="./img/steve-jobs.jpg" alt="">
</div>
</div>
複製代碼
此時 img 標籤是沒有 src 屬性的,咱們把真實的圖片地址放在一個屬性裏,這裏咱們使用 HTML5 的 data
屬性,將真實地址放在自定義的 data-src
中。瀏覽器
這一邏輯有兩種方法,聽我娓娓道來。性能優化
方法一app
第一種方法咱們經過計算該圖片距離 document
頂部的高度是否小於當前可視區域相對於 document 頂部高度來判斷。dom
可視區域相對於 document 頂部高度的計算方法:
const clientHeight = document.documentElement.clientHeight; // 視口高度,也就是窗口的高度。
const scrollHeight = document.documentElement.scrollTop + clientHeight; // 滾動條偏移文檔頂部的高度(也就是文檔從頂部開始到可視區域被抹去的高度) + 視口高度
複製代碼
畫了一張圖方便理解:
而後就是計算該圖片距離文檔頂部的高度。有兩種方法,第一種方法是經過元素的 offsetTop
屬性來計算。從上圖咱們瞭解到元素的 offsetTop 屬性是相對於一個 position
爲 非 static
的祖先元素,也就是 child.offsetParent
。同時須要將祖先元素的 border
考慮在內,咱們經過child.offsetParent.clientTop
能夠拿到邊框厚度。
由此咱們獲得元素距離文檔頂部的高度的計算方法:
function getTop(el, initVal) {
let top = el.offsetTop + initVal;
if (el.offsetParent !== null) {
top += el.offsetParent.clientTop;
return getTop(el.offsetParent, top);
} else {
return top;
}
}
複製代碼
這裏的這個方法使用了 尾遞歸調用
。能夠提升遞歸性能。固然這裏也能夠用循環來實現:
function getTop(el) {
let top = el.offsetTop;
var parent = el.offsetParent;
while(parent !== null) {
top += parent.offsetTop + parent.clientTop;
parent = parent.offsetParent;
}
return top;
}
複製代碼
第二種方法是使用 element.getBoundingClientRect()
API 直接獲得 top 值。
getBoundingClientRect 的返回值以下圖:
var first = document.getElementById('first');
getTop(first, 0); // 130
console.log(first.getBoundingClientRect().top); // 130
複製代碼
因而咱們獲得判斷方法:
function inSight(el) {
const clientHeight = document.documentElement.clientHeight;
const scrollHeight = document.documentElement.scrollTop + clientHeight;
// 方法一
return getTop(el, 0) < scrollHeight;
// 方法二
// return el.getBoundingClientRect().top < clientHeight;
}
複製代碼
接下來就是對每一個圖片進行判斷和賦值。
function loadImg(el) {
if (!el.src) {
el.src = el.dataset.src;
}
}
function checkImgs() {
const imgs = document.getElementsByTagName('img');
Array.from(imgs).forEach(el => {
if (inSight(el)) {
loadImg(el);
}
})
console.log(count++);
}
複製代碼
最後給 window 綁定 onscroll
事件以及 onload
事件:
window.addEventListener('scroll', checkImgs, false);
window.onload = checkImgs;
複製代碼
咱們知道相似 scroll
或 resize
這樣的事件瀏覽器可能在很短的時間內觸發不少次,爲了提升網頁性能,咱們須要一個節流函數來控制函數的屢次觸發,在一段時間內(如 500ms)只執行一次回調。
/** * 持續觸發事件,每隔一段時間,只執行一次事件。 * @param fun 要執行的函數 * @param delay 延遲時間 * @param time 在 time 時間內必須執行一次 */
function throttle(fun, delay, time) {
var timeout;
var previous = +new Date();
return function () {
var now = +new Date();
var context = this;
var args = arguments;
clearTimeout(timeout);
if (now - previous >= time) {
fun.apply(context, args);
previous = now;
} else {
timeout = setTimeout(function () {
fun.apply(context, args);
}, delay);
}
}
}
window.addEventListener('scroll', throttle(checkImgs, 200, 1000), false);
複製代碼
方法二
HTML5 有一個新的 IntersectionObserver
API,它能夠自動觀察元素是否可見。
主要用法:
var observer = new IntersectionObserver(callback, option);
// 開始觀察
observer.observe(document.getElementById('first'));
// 中止觀察
observer.unobserve(document.getElementById('first'));
// 關閉觀察器
observer.disconnect();
複製代碼
目標的可見性發生變化時就會調用觀察器的 callback。
function callback(changes: IntersectionObserverEntry[]) {
console.log(changes[0])
}
// IntersectionObserverEntry
{
time: 29.499999713152647,
intersectionRatio: 1,
boundingClientRect: DOMRectReadOnly {
bottom: 144,
height: 4,
left: 289,
right: 293,
top: 140,
width: 4,
x: 289,
y: 140
},
intersectionRect: DOMRectReadOnly,
isIntersecting: true,
rootBounds: DOMRectReadOnly,
target: img#first
}
複製代碼
詳細釋義:
使用 IntersectionObserver 實現圖片懶加載:
function query(tag) {
return Array.from(document.getElementsByTagName(tag));
}
var observer = new IntersectionObserver(
(changes) => {
changes.forEach((change) => {
if (change.intersectionRatio > 0) {
var img = change.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
})
}
)
query('img').forEach((item) => {
observer.observe(item);
})
複製代碼
完整代碼見 github
完 :)