圖片延時加載十分重要,尤爲是對於移動端用戶。javascript
從理論上來看,圖像延遲加載機制十分簡單,但實際上卻有不少須要注意的細節。 此外,有多個不一樣的用例均受益於延遲加載。 首先,咱們來了解一下在 HTML 中延遲加載內聯圖像。java
延遲加載是一種在加載頁面時,延遲加載非關鍵資源的方法, 而這些非關鍵資源則在須要時才進行加載。 就圖像而言,「非關鍵」一般是指「屏幕外」。node
最近在作一個移動端漫畫應用(id.mangaya.mobi),涉及的圖片比較多,若是圖片不作額外處理,會對用戶不太友好,並且lighthouse評分也會所以下降。react
主要有兩種場景。git
有興趣的同窗能夠查看react-progressive-lazy-image,使用起來很是簡單!github
Senario 1: 圖片延時到在窗口viewport內纔開始加載。數組
Senario 2: 用戶觀看漫畫,須要漫畫一張張的順序加載。瀏覽器
圖片懶加載技術主要經過監聽圖片資源容器是否出如今視口區域內,來決定圖片資源是否被加載。bash
那麼實現圖片懶加載技術的核心就是如何判斷元素處於視口區域以內。ide
那麼如何實現呢?
返回的值是一個DOMRect對象,它是getClientRects()元素返回的矩形的並集,即與元素關聯的CSS邊框。其結果是,其包含整個元件,具備只讀的最小矩形left,top,right,bottom,x,y,width,和height性質描述在像素總體邊界框。除視口左上角width和height相對於視口左上角的屬性。
//https://gomakethings.com/how-to-test-if-an-element-is-in-the-viewport-with-vanilla-javascript/
export const elementIsInsideViewport = el => {
const bounding = el.getBoundingClientRect();
return (
bounding.top >= 0 &&
bounding.left >= 0 &&
bounding.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
bounding.right <=
(window.innerWidth || document.documentElement.clientWidth)
);
};
複製代碼
能夠結合window.onScroll以及window.onResize事件以及throttle來實現對img元素的判斷。
document.addEventListener("DOMContentLoaded", function() {
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
let active = false;
const lazyLoad = function() {
if (active === false) {
active = true;
setTimeout(function() {
lazyImages.forEach(function(lazyImage) {
if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
lazyImage.classList.remove("lazy");
lazyImages = lazyImages.filter(function(image) {
return image !== lazyImage;
});
if (lazyImages.length === 0) {
document.removeEventListener("scroll", lazyLoad);
window.removeEventListener("resize", lazyLoad);
window.removeEventListener("orientationchange", lazyLoad);
}
}
});
active = false;
}, 200);
}
};
document.addEventListener("scroll", lazyLoad);
window.addEventListener("resize", lazyLoad);
window.addEventListener("orientationchange", lazyLoad);
});
複製代碼
此代碼在 scroll 事件處理程序中使用 getBoundingClientRect 來檢查是否有任何 img.lazy 元素處於視口中。 使用 setTimeout 調用來延遲處理,active 變量則包含處理狀態,用於限制函數調用。 延遲加載圖像時,這些元素隨即從元素數組中移除。 當元素數組的 length 達到 0 時,滾動事件處理程序代碼隨即移除。
雖然此代碼幾乎可在任何瀏覽器中正常運行,但卻存在潛在的性能問題,即重複的 setTimeout 調用可能純屬浪費,即便其中的代碼受限制,它們仍會運行。 在此示例中,當文檔滾動或窗口調整大小時,無論視口中是否有圖像,每 200 毫秒都會運行一次檢查。 此外,跟蹤還沒有延遲加載的元素數量,以及取消綁定滾動事件處理程序的繁瑣工做將由開發者來完成。
此方法很是簡單,只須要爲元素生成一個IntersectionObserver,而且監聽該元素,而後在監聽的回調判斷元素的intersectionRatio比率便可達到所需。這是核心代碼.
componentDidMount() {
const { src, needLazyUtilInViewPort, canLoadRightNow } = this.props;
if (needLazyUtilInViewPort) {
//https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
try {
const node = ReactDOM.findDOMNode(this);
this.observer = new IntersectionObserver(this.insideViewportCb);
this.observer.observe(node);
} catch (err) {
console.log("err in finding node", err);
}
} else {
if (canLoadRightNow) {
this.loadImage(src);
}
}
}
insideViewportCb(entries) {
entries.forEach(element => {
//在viewport裏面
if (element.intersectionRatio >0) {
this.loadImage(this.props.src);
}
});
}
複製代碼
不過,Intersection Observer 的缺點是雖然在瀏覽器之間得到良好的支持,但並不是全部瀏覽器皆提供支持。 對於不支持 Intersection Observer 的瀏覽器,您可使用 polyfill,或者如以上代碼所述,檢測 Intersection Observer 是否可用,並在其不可用時回退到兼容性更好的舊方法。
至於列表圖順序加載的話,只須要在每一個圖片回調通知父組件能夠加載下一張就能夠了。 總的來講getBoundingClientRect和Intersection Observer均可以實現圖片懶加載,可是getBoundingClientRect若是在當前頁面使用到其餘onScroll事件,會出現卡頓等問題,不能很是順暢的滑動,而Intersection Observer使用起來很是簡單流暢。
圖像延遲加載 && 列表圖順序加載的組件已經開源啦~!
有興趣的同窗能夠查看react-progressive-lazy-image,使用起來很是簡單!