图片懒加载不完全指南
图片懒加载不完全指南
图片懒加载
在日常开发中,我们常用的两种图片加载方式如下:
- 使用
img
标签加载图片; - 使用
css background
加载图片。
在这篇文章中,您将了解如何延迟加载这两种类型的图像。
img 标签图片懒加载
对于 img 标签,我们有三个延迟加载方案,同时也可以组合使用以实现跨浏览器的最佳兼容性:
- 使用浏览器提供的 API:
loading="lazy"
- 使用
Intersection Observer
- 使用 scroll 和 resize 事件进行处理
使用浏览器级别的懒加载
Chrome 和 Firefox 都支持使用 loading
属性的延迟加载。把此属性添加到 <img>
元素并设置其值为 lazy
相当于告诉浏览器,如果图像在可视区域中,就立即加载它,反之则不加载。
至于加载的视口距离阈值,目前是没办法修改的,chrome 会根据当前的网络情况设置对应的阈值。
对于2020 年 7 月以后的 chrome版本,4G 网络下的视口距离阈值是 1250 像素,3G 网络下是 2500 像素。
需要注意的是我们也需要手动指定图片的宽高,否则我们可能会看到布局发生偏移。一个完整的代码实例如下:
<img src="image.png" loading="lazy" alt="…" width="200" height="200">
demo 如下:
有关浏览器支持的详细信息,可以参阅MDN的浏览器兼容性表。如果浏览器不支持延迟加载,那么该属性将被忽略,图像将像往常一样立即加载。
个人觉得它的缺点或者不够好的地方有以下几点:
- 不能自定义加载的视口距离阈值;
- 官方建议首屏内的图片不设置
loading="lazy"
, 这需要一些额外的使用成本; - 无法设置加载中情况下的占位图;
- 暂时没有对应的 polyfil,需要我们自己结合下面介绍的两种方式之一组合使用。
有关 loading
属性更详细的内容请阅读这篇文章。
使用 Intersection Observer
我们需要使用 JavaScript 来判断图片是否在视图中,如果是,则将真实的 url 地址填充到图片的 src 中。如果之前了解过延迟加载,可能我们想到的是通过 scroll 事件来判断图片是否达到可视条件。虽然这种方法是兼容性最好的,但现代浏览器提供了一种性能更高、效率更高的方法:通过 Intersection Observer API
来完成检查元素可见性的工作。
相比于依赖各种事件处理程序的实现方式,Intersection Observer
更易于使用和阅读,因为您只需注册一个观察者即可观察元素,而无需编写繁琐的元素可见性检测代码。剩下要做的就是决定当元素可见时要做什么。
它的语法如下:
const observer = new IntersectionObserver(callback, options)
// options 支持以下选项:
// {
// root: null, // 所监听对象的具体根元素。如果未传入值或值为null,则默认使用顶级文档的视窗。
// rootMargin: "30px 100px 50px", // 可以简单理解为增大根元素的范围,用来修改可见的触发范围
// thresholds: [0, 0.5, 1], // 用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是[0]。
// 上面代码,我们指定了交叉比例为0,0.5,1,当观察元素img0%、50%、100%时候就会触发回调函数
// }
假设我们有如下图片元素:
<img
class="lazy"
src="placeholder-image.jpg"
data-src="image-to-lazy-load-1x.jpg"
data-srcset="image-to-lazy-load-2x.jpg 2x, image-to-lazy-load-1x.jpg 1x"
alt="I'm an image!"
>
我们需要关注以下三点:
class
属性,我们用来获取所有需要懒加载的图片元素;src
属性,引用占位图片;data-src
和data-srcset
属性,它们是占位符属性,包含元素在进入可视区后将加载的图像的 URL。
接下里我们看如何使用Intersection Observer
来实现懒加载:
// 1. 在DOMContentLoaded事件触发时,获取所有需要懒加载的 img 标签。
document.addEventListener("DOMContentLoaded", function() {
var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
// 2. 如果IntersectionObserver可用,创建一个新的观察者,
// 当 img.lazy 元素进入视口时运行回调
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
lazyImage.classList.remove("lazy");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
} else {
// 不支持IntersectionObserver环境下的 fallback
}
});
使用事件进行处理
如果浏览器不支持上面所说的Intersection Observer
,最简答的办法就是使用它的 polyfill,或者回退到使用 scroll
、resize
和可能用到的orientationchange
事件配合getBoundingClientRect
方法来确定元素是否在可视区域中。
实现的思路如下:假设与之前的 img 标签示例相同,我们还是使用 lazy 这个 class 来获取要懒加载的图片元素,使用 setTimeout 和 active 标记 来做节流,当一个元素加载之后,将其从列表中移除。当整个需要懒加载的列表都被清空后,移除对事件的监听。示例代码如下:
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);
});
css background 图片懒加载
和 img 标签实现方式类似,我们可以使用 JS 来判断元素是否可见,然后更改元素的 class 来实现。
举个简单的例子:
// index.css
.lazy-background {
background-image: url("hero-placeholder.jpg"); /* Placeholder image */
}
.lazy-background.visible {
background-image: url("hero.jpg"); /* The final image */
}
// lazy-background.js
document.addEventListener("DOMContentLoaded", function() {
var lazyBackgrounds = [].slice.call(document.querySelectorAll(".lazy-background"));
if ("IntersectionObserver" in window) {
let lazyBackgroundObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
lazyBackgroundObserver.unobserve(entry.target);
}
});
});
lazyBackgrounds.forEach(function(lazyBackground) {
lazyBackgroundObserver.observe(lazyBackground);
});
}
});
参考文章