概念
图片懒加载实际上是一种网页优化技术。同普通静态资源一样,图片在被请求时,也会占用网络资源。若是一次性将整个页面的所有图片都加载完,这将大大增加页面的首屏加载时间(给人很卡很慢的现象)。为了解决这种问题,开发人员让图片仅在浏览器当前视窗内出现时才进行加载。而这种减少首屏图片请求数,且根据当前视口加载图片的的技术,就被称为“图片懒加载”。
上面说的可能有点绕,这里换句通俗的话:图片懒加载就是鼠标滑动到哪里,图片就加载到哪里。 当然,这话说得不是很严谨,但是胜在易理解。
实现思路
- 在 img 标签上自定义一个属性 data-src,用于存放真正需要显示的图片路径,而 img 的 src 属性上放一张大小为 1 * 1px 的图片路径,或者为空。
- 当页面滚动直至某个图片出现在可视区域时,就通过 js 获取该图片的 data-src 的值,然后赋给它的 src。
注意:自定义属性可以取任何名字,但要符合规范。
代码实现
方案一
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>图片懒加载</title>
<style>
img {
width: 100%;
height: 700px;
}
</style>
</head>
<body>
<!-- 网图链接若是失效,可自行替换 -->
<img src="https://pic.netbian.com/uploads/allimg/211120/005250-1637340770973a.jpg" alt="1" />
<img src="https://pic.netbian.com/uploads/allimg/210920/165135-16321278956369.jpg" alt="2" />
<img data-src="https://pic.netbian.com/uploads/allimg/211122/223735-1637591855c6b1.jpg" alt="3" />
<img data-src="https://pic.netbian.com/uploads/allimg/211021/232902-16348301422b2a.jpg" alt="4" />
<img data-src="https://pic.netbian.com/uploads/allimg/211109/221940-1636467580f68a.jpg" alt="5" />
<!-- Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js"></script>
<script>
const imgs = document.querySelectorAll('img');
const imgList = Array.prototype.slice.call(imgs);
const clientHeight = document.documentElement.clientHeight; // 视口高度
const lazyLoad = () => {
imgList.forEach(item => {
// 判断图片出现在了当前视口的条件:
// img元素的 CSSOM 对象到视口顶部的距离 < 视口高度 + 100px,加上 100px 是为了提前触发图片加载
if (item.getBoundingClientRect().top < clientHeight + 100) {
if ('src' in item.dataset) {
item.src = item.dataset.src;
}
}
});
};
// 使用节流器(throttle),提高性能
document.addEventListener('scroll', _.throttle(lazyLoad, 200));
</script>
</body>
</html>
相关知识点:
-
Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。
-
Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库(非常强大)。这里仅使用其封装的节流方法——
throttle
,来提高性能。
若是对函数的节流与防抖比较感兴趣的话,不妨看看本人写的文章。
方案二
const imgs = document.querySelectorAll('img');
const imgList = Array.prototype.slice.call(imgs);
const clientHeight = document.documentElement.clientHeight; // 视口高度
const lazyLoad = () => {
// 滚动距离
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
imgList.forEach(item => {
// 判断图片出现在了当前视口的条件:
// 图片到浏览器顶部的距离 < 视口高度 + 滚动距离
if (item.offsetTop < clientHeight + scrollTop && !item.src) {
item.src = item.dataset.src;
}
});
};
// 使用节流器(throttle),提升性能
document.addEventListener('scroll', _.throttle(lazyLoad, 200));
同方案一相比,方案二代码主要是对 lazyLoad
函数进行了一些更改,其它基本不变。在这个函数中不在使用 getBoundingClientRect()
方法,而是通过 HTMLElement.offsetTop 和 document.documentElement.scrollTop 来实现图片的懒加载。
方案三
Intersection Observer API 提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。通过这个 API 也可以实现图片懒加载,但是它的兼容性不是很好。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>图片懒加载</title>
<style>
.img-item {
width: 100%;
height: 700px;
}
.img-item > img {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div class="img-item">
<img data-src="https://pic.netbian.com/uploads/allimg/211120/005250-1637340770973a.jpg" alt="1" />
</div>
<div class="img-item">
<img data-src="https://pic.netbian.com/uploads/allimg/210920/165135-16321278956369.jpg" alt="2" />
</div>
<div class="img-item">
<img data-src="https://pic.netbian.com/uploads/allimg/211122/223735-1637591855c6b1.jpg" alt="3" />
</div>
<div class="img-item">
<img data-src="https://pic.netbian.com/uploads/allimg/211021/232902-16348301422b2a.jpg" alt="4" />
</div>
<script>
const imgs = document.querySelectorAll('img');
const imgList = Array.from(imgs);
const options = {
root: null,
rootMargin: '0px',
threshold: [0.15],
};
const ob = new IntersectionObserver(imgs => {
// imgs 为目标元素集合
imgs.forEach(item => {
// isIntersecting 代表目标元素可见
if (item.isIntersecting) {
console.log(item, '显示');
const img = item.target;
img.src = img.dataset.src;
ob.unobserve(img); // 停止对一个元素的观察
}
});
}, options);
imgList.forEach(item => {
// 可见性区域被监控的元素。此元素必须是根元素的后代 (如果根元素为视窗,则该元素必须被当前文档包含)。
ob.observe(item);
});
</script>
</body>
</html>
注意,dom结构中,我们给每个 img 元素添加一个父级元素并设置宽高。其目的,是因为加载 img 元素时,若是其 src 属性是空或不存在,就会变成一个非常小的正方形图标。这样的话,所有待加载的 img 就全部出现在可视区域之中,其结果就是加载所有图片而不是进行懒加载。
看下面这段代码:
imgList.forEach(item => {
ob.observe(item);
});
这段代码就是为了监控每个出现在可视区域的元素以便执行回调,若是所有元素都出现在可视区域,那么它将性触发所有元素的回调,即加载所有的图片。