目录
前言:
面试时碰到了这个问题,之前练手项目的时候用过,但没有深入了解其底层原理,所以面试结果应该只能唱一首凉凉了,现做一个总结,后面继续加油,也希望大家吸取教训
懒加载是什么?
懒加载肯定重点突出一个‘懒’字,我们来从发现什么问题,然后怎么解决去解决的角度来看
首先是有什么问题呢?在我们访问一个图片展示比较多的网页时,加载速度慢很多时候正是因为图片多导致,大量的img图片导致页面渲染的堵塞。当费了许多力气把全部图片和页面加载出来时而用户早已离去。另一方面,若用户只查看了网页的前面部分便离开,许多已经加载却因为处于网页底部而未呈现在视口区的图片,它们极大加重服务器压力了但是用户看都没看,白白浪费了性能。
为了解决上面的问题需要引入图片懒加载,当用户滚动相应可视区域,若可视区域有图片便加载,而在可视区域外未加载过的图片它们先不加载,如果用户滚动可视区域到它们时它们再加载,否则一律不加载。这样一来就大大提高了网页渲染的性能和减少不必要的浪费。
基础知识
- window.innerHeight 是浏览器可视区的高度;
- document.body.scrollTop || document.documentElement.scrollTop是浏览器滚动的过的距离;
- imgs.offsetTop 是元素顶部距离文档顶部的高度(包括滚动条的距离);
- 内容达到显示区域的: img.offsetTop < window.innerHeight + document.body.scrollTop;
实现方式
实现图片的懒加载有两个思路:
- 事件监听:监听scroll事件,鼠标滚动就触发
IntersectionObserver
:按字面意思就是交叉观察,也就是目标元素和可视窗口会产生交叉区域,观察交叉区域发生什么事情,执行什么程序
IntersectionObserver
IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。
Intersection Observer
可以不用监听scroll
事件,做到元素一可见便调用回调,在回调里面我们来判断元素是否可见。
(一)监听scroll
事件
就像我们上面的基础知识所说的,可以用img.offsetTop < window.innerHeight + document.body.scrollTop;判断图片是否到达可视区域
不过这个方案有个很大的问题,就是会多次触发,最好和节流一起使用
<!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: 1244px;
height: 800px;
}
</style>
</head>
<body>
<h3>图片懒加载方法一</h3>
<!-- 图片,用一个其他属性存储真正的图片地址: -->
<img data-src="img/1.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/2.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/3.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/4.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/5.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/6.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/7.webp" src="img/0.webp" alt="xxx" />
<script>
const images = document.querySelectorAll("img");
// lazyload(); //页面载入完毕加载可视区域内的图片 要看效果的话还是不加比较好
// 节流函数,保证每200ms触发一次
function throttle(fn, delay) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(this, args)
}, delay)
}
}
}
window.addEventListener('scroll', throttle(lazyload, 500))
function lazyload() { //监听页面滚动事件
var seeHeight = window.innerHeight; //可见区域高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop; //滚动条距离顶部高度
for (let i of images) {
//判断当前图片是否在可视区内
if (i.offsetTop <= seeHeight+scrollTop) {
//getAttribute得到某个元素的属性值,获取自定义data-src属性的值
let trueSrc = i.getAttribute("data-src");
//setAttribute设置某个属性的属性值,把值赋值给图片的src属性
i.setAttribute("src", trueSrc);
}
}
// for (let i of images) {
// //计算方式和第一种方式不同
// if (i.getBoundingClientRect().top < window.innerHeight) {
// let trueSrc = i.getAttribute("data-src");
// i.setAttribute("src", trueSrc);
// }
// } 其中,getBoundingClientRect().top 为元素相对于窗口的位置;window.innerHeight 为当前窗口的高度;
// 当元素对于窗口的位置小于当前窗口的高度时,那自然处于了窗口可视区了。
}
</script>
</body>
</html>
(二)IntersectionObserver
当一个IntersectionObserver
对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦IntersectionObserver
被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。
<!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: 1244px;
height: 800px;
}
</style>
</head>
<body>
<h3>图片懒加载方法一</h3>
<!-- 图片,用一个其他属性存储真正的图片地址: -->
<img data-src="img/1.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/2.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/3.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/4.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/5.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/6.webp" src="img/0.webp" alt="xxx" />
<img data-src="img/7.webp" src="img/0.webp" alt="xxx" />
<script>
const images = document.querySelectorAll("img");
// 传给IntersectionObserver的回调函数
// 在目标元素能看见时触发一次,目标元素看不见了时再触发一次
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const image = entry.target;
const data_src = image.getAttribute("data-src");
image.setAttribute("src", data_src);
// 图片被加载后取消观察
observer.unobserve(image);
}
});
});
images.forEach(image => {
observer.observe(image);
});
</script>
</body>
</html>
(三)<img/>标签添加属性loading="lazy"
chrome支持,其他浏览器暂时不支持
<!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: 1244px;
height: 800px;
}
</style>
</head>
<body>
<h3>图片懒加载方法三</h3>
<!-- 图片,用一个其他属性存储真正的图片地址: -->
<img src="img/1.webp" alt="xxx" loading="lazy"/>
<img src="img/2.webp" alt="xxx" loading="lazy"/>
<img src="img/3.webp" alt="xxx" loading="lazy"/>
<img src="img/4.webp" alt="xxx" loading="lazy"/>
<img src="img/5.webp" alt="xxx" loading="lazy"/>
<img src="img/6.webp" alt="xxx" loading="lazy"/>
<img src="img/7.webp" alt="xxx" loading="lazy"/>
</body>
</html>
上述讲解代码可以从https://github.com/guodongO/example下载