什么是圖片懶加載
進(jìn)入頁面的時(shí)候,只請(qǐng)求可視區(qū)域的圖片資源
懶加載原理
圖片的標(biāo)簽是 img 標(biāo)簽,圖片的來源主要是 src 屬性,瀏覽器是否發(fā)起加載圖片的請(qǐng)求是根據(jù)是否有src屬性決定的。
所以可以從 img 標(biāo)簽的 src 屬性入手,在沒進(jìn)到可視區(qū)域的時(shí)候,就先不給 img 標(biāo)簽的 src 屬性賦值。
懶加載實(shí)現(xiàn)
- 監(jiān)聽 scroll 事件判斷元素是否進(jìn)入視口
const imgs = [...document.getElementsByTagName('img')];
let n = 0;
lazyload();
function throttle(fn, wait) {
let timer = null;
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null;
fn.apply(this, args)
}, wait)
}
}
}
function lazyload() {
var innerHeight = window.innerHeight;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for (let i = n; i < imgs.length; i++) {
if (imgs[i].offsetTop < innerHeight + scrollTop) {
imgs[i].src = imgs[i].getAttribute("data-src");
n = i + 1;
}
}
}
window.addEventListener('scroll', throttle(lazyload, 200));
存在問題:
- 每次滑動(dòng)都要執(zhí)行一次循環(huán),如果有1000多個(gè)圖片,性能會(huì)很差
- 每次讀取 scrollTop 都會(huì)引起回流
- scrollTop 跟 DOM 的嵌套關(guān)系有關(guān),應(yīng)該根據(jù) getboundingclientrect 獲?。╣etBoundingClientRect 用于獲取某個(gè)元素相對(duì)于視窗的位置集合,集合中有top, right, bottom, left 等屬性)
- 滑到最后的時(shí)候刷新,會(huì)看到所有的圖片都加載了
tips:
當(dāng) render tree 中的一部分(或全部)因?yàn)樵氐囊?guī)模尺寸,布局,隱藏等改變而需要重新構(gòu)建,稱為回流。每個(gè)頁面至少需要一次回流,就是在頁面第一次加載的時(shí)候。
當(dāng) render tree 中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風(fēng)格,而不會(huì)影響布局的,比如 background-color,稱為重繪。
注:回流必將引起重繪,而重繪不一定會(huì)引起回流。
-
IntersectionObserver
IntersectionObserver接口提供了一種異步觀察目標(biāo)元素與其祖先元素或頂級(jí)文檔視窗(viewport)交叉狀態(tài)的方法。祖先元素與視窗(viewport,可視視口)被稱為根(root)。當(dāng)一個(gè)
IntersectionObserver對(duì)象被創(chuàng)建時(shí),其被配置為監(jiān)聽根中一段給定比例的可見區(qū)域。一旦 IntersectionObserver 被創(chuàng)建,則無法更改其配置,所以一個(gè)給定的觀察者對(duì)象只能用來監(jiān)聽可見區(qū)域的特定變化值;然而,你可以在同一個(gè)觀察者對(duì)象中配置監(jiān)聽多個(gè)目標(biāo)元素。
const imgs = [ ...document.getElementsByTagName('img') ];
// 當(dāng)監(jiān)聽的元素進(jìn)入可視范圍內(nèi)的會(huì)觸發(fā)回調(diào)
if (IntersectionObserver) {
// 創(chuàng)建一個(gè) intersection observer
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
let lazyImage = entry.target;
// 相交率,默認(rèn)是相對(duì)于瀏覽器視窗
if (entry.intersectionRatio > 0) {
lazyImage.src = lazyImage.getAttribute('data-src');
// 當(dāng)前圖片加載完之后需要去掉監(jiān)聽
lazyImageObserver.unobserve(lazyImage);
}
})
})
for (let i = 0; i < imgs.length; i++) {
lazyImageObserver.observe(imgs[i]);
}
}
- vue自定義指令-懶加載
<img v-lazyload="image">
Vue.directive('lazyload', {
// 指令的定義
bind: function(el, binding) {
let lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
let lazyImage = entry.target;
// 相交率,默認(rèn)是相對(duì)于瀏覽器視窗
if(entry.intersectionRatio > 0) {
lazyImage.src = binding.value;
// 當(dāng)前圖片加載完之后需要去掉監(jiān)聽
lazyImageObserver.unobserve(lazyImage);
}
})
})
lazyImageObserver.observe(el);
},
});