图片延时加载十分重要,尤为是对于移动端用户。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,使用起来很是简单!