概念
图片懒加载是指在初始化页面时只加载出现在视口内(或者接近视口)的图片,其它的图片等待用户操作网页将其滚动到视口时,再进行加载。通过延迟加载图片,能够减小了网页首屏需要加载的资源数,优化了网页的首屏加载性能。
实现方案
监听滚动事件
通过监听浏览器的 scroll、resize 和 orientationchange 事件,判断用户操作后图片位于视口内部(Image.getBoundingClientRect().top < window.innerHeight && Image.getBoundingClientRect().bottom >= 0)时,就将自定义属性 data-src 中的图片 url 赋值给 src 属性,开始加载图片资源。
注意:由于 scroll 和 resize 事件的触发频率较高,频繁的计算会比较影响页面的性能,所以需要通过节流来优化代码的性能。
实现代码:
document.addEventListener('DOMContentLoaded', function () {
// 获取所有图片元素
let images = [].slice.call(document.querySelectorAll('img'));
// 利用标记进行节流,减少触发次数
let active = false;
const loadImage = function () {
if (!active) {
active = true;
setTimeout(function () {
images.forEach(function (lazyImage) {
// 判断图片位于视口内部
if (
lazyImage.getBoundingClientRect().top < window.innerHeight &&
lazyImage.getBoundingClientRect().bottom >= 0 &&
getComputedStyle(lazyImage).display !== 'none'
) {
// 从自定义属性中获取图片路径,加载图片资源
const { src, srcset } = lazyImage.dataset;
src && (lazyImage.src = src);
srcset && (lazyImage.srcset = lazyImage.dataset.srcset);
// 移除已加载的图片
images = images.filter(function (image) {
return image !== lazyImage;
});
// 所有图片加载完毕,移除事件监听
if (images.length === 0) {
document.removeEventListener('scroll', loadImage);
window.removeEventListener('resize', loadImage);
window.removeEventListener('orientationchange', loadImage);
}
}
});
active = false;
}, 200);
}
};
// 手动触发
loadImage();
// 添加事件监听
document.addEventListener('scroll', loadImage);
window.addEventListener('resize', loadImage);
window.addEventListener('orientationchange', loadImage);
});
Intersection Observer
相较于使用事件监听的方案,Intersection Observer 更容易理解和使用。因为只需要注册一个观察者即可观察元素,通过被观察者对象的 isIntersecting 属性即可判断图片是否出现在了视口内,而无需编写繁琐的元素可见性检测代码,所以性能会更好。
注意:IE 浏览器不兼容。
实现代码:
document.addEventListener('DOMContentLoaded', function () {
// 获取所有图片元素
let images = [].slice.call(document.querySelectorAll('img'));
// 初始化观察者对象
const imageObserver = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
const lazyImage = entry.target;
// 从自定义属性中获取图片路径,加载图片资源
const { src, srcset } = lazyImage.dataset;
src && (lazyImage.src = src);
srcset && (lazyImage.srcset = lazyImage.dataset.srcset);
// 移除观察对象
imageObserver.unobserve(lazyImage);
}
});
});
images.forEach(function (image) {
// 观察图片元素
imageObserver.observe(image);
});
});
原生懒加载特性
通过指定 img 标签的 loading 属性为 lazy,即可开启浏览器原生支持的图片懒加载特性。使用该方案时浏览器会自行判断图片加载的时机,并且浏览器还针对弱网环境做了相关的优化处理。
注意:该方案的兼容性最差。
实现代码:
<img class="image" loading="lazy" src="https://picsum.photos/200/200" />
最终方案
结合了以上的三种方案,通过特性检查判断,优先使用浏览器的 loading 特性,然后是 Intersection Observer,最后才使用事件监听的方案来进行兜底。
实现代码:
<img class="image" loading="lazy" data-src="https://picsum.photos/200/200" />
document.addEventListener('DOMContentLoaded', function () {
let images = [].slice.call(document.querySelectorAll('img'));
if ('loading' in HTMLImageElement.prototype) {
images.forEach((image) => {
const { src, srcset } = image.dataset;
src && (image.src = src);
srcset && (image.srcset = image.dataset.srcset);
});
} else if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
const lazyImage = entry.target;
const { src, srcset } = lazyImage.dataset;
src && (lazyImage.src = src);
srcset && (lazyImage.srcset = lazyImage.dataset.srcset);
imageObserver.unobserve(lazyImage);
}
});
});
images.forEach(function (image) {
imageObserver.observe(image);
});
} else {
let active = false;
const loadImage = function () {
if (!active) {
active = true;
setTimeout(function () {
images.forEach(function (lazyImage) {
if (
lazyImage.getBoundingClientRect().top < window.innerHeight &&
lazyImage.getBoundingClientRect().bottom >= 0 &&
getComputedStyle(lazyImage).display !== 'none'
) {
const { src, srcset } = lazyImage.dataset;
src && (lazyImage.src = src);
srcset && (lazyImage.srcset = lazyImage.dataset.srcset);
images = images.filter(function (image) {
return image !== lazyImage;
});
if (images.length === 0) {
document.removeEventListener('scroll', loadImage);
window.removeEventListener('resize', loadImage);
window.removeEventListener('orientationchange', loadImage);
}
}
});
active = false;
}, 200);
}
};
loadImage();
document.addEventListener('scroll', loadImage);
window.addEventListener('resize', loadImage);
window.addEventListener('orientationchange', loadImage);
}
});
可选的第三方库
- lazysizes 是一个功能齐全的延迟加载库,可以延迟加载图像和 iframe。
- vanilla-lazyload 可用于延迟加载图像、背景图像、视频、iframe 和脚本,相比于 lazysizes 会更加轻量。
- vue-lazyload 是 Vue 的图片懒加载方案
- react-lazyload 是 React 的图片懒加载方案