在研究图片懒加载前先要知道图片懒加载的实现原理。
- 图片懒加载的实现原理就是判断元素是否在浏览器可视范围,如果在可视范围则设置img的src为真实地址。如果未在可视区域则显示默认的图片,如加载中等的字样。(你问我什么是可视范围?别问,问就是自己百度。)。当然这个判断元素是否在可视范围不仅仅能做图片懒加载,其它用途也挺过,比如无限滚动,虚拟列表等。
- 那么知道原理后就好办了,现在就需要知道如何判断img是否在可视范围,这里我个人有三种方案,客观请往下看。
判断元素是否在可视范围(三种)
- 方案一 (手动计算法)
固定判断公式:
el.offsetTop - document.documentElement.scrollTop <= viewPortHeight
封装为函数,(小二,上代码)。
(来咯,客观你要的函数,请慢用)
function isInViewPortOfOne (el) {
// 得到当前浏览器得可视区域高度,(不包含滚动条及隐藏区域)
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
// 得到目标元素距离视图顶部得距离
const offsetTop = el.offsetTop
// 得到滚动条距离顶部得高度
const scrollTop = document.documentElement.scrollTop
// 用offsetTop-scrollTop如果小于或者等于视图的高度那么证明元素在可视区域
const top = offsetTop - scrollTop
return top <= viewPortHeight
}
- 方案二 Api (el.getBoundingClientRect())
function isInViewPort(element) {
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
// top=得到目标元素距离视图顶部得距离
const {
top,
right,
bottom,
left,
} = element.getBoundingClientRect();
return (
top >= 0 &&
left >= 0 &&
right <= viewWidth &&
bottom <= viewHeight
);
}
- 方案三 观察者模式 IntersectionObserver 构造函数
关于IntersectionObserver的相关文档请移步到 点我
const options = {
// 表示重叠面积占被观察者的比例,从 0 - 1 取值,
// 1 表示完全被包含
threshold: [0],
// 类似css的margin 用于标记观观察元素元父容器的margin交会时触发。
// 比如现在是 -100px 那么现在img就会在距离视图底部-100px时触发并切换src,简单理解类似margin-bottom:100
rootMargin: '-150px' // 这个-150是为了更加直观的感受到懒加载,数字越大触发时间越早
};
const callback = function(entries, observer) {
entries.forEach(entry => {
entry.isIntersecting // 当前元素是否在可视区域,如果在可视区域则可以进行处理需要的逻辑
entry.target; // 被观察者
});
};
const observer = new IntersectionObserver(callback, options);
const target = document.querySelector('.spanel');
// 开始监听元素
observer.observe(target);
实现案例 (两种实现方式)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.zwbox{
}
.spanel{
height: 200px;
width: 200px;
margin: 10px;
transition: all 1s;
}
.container{
}
</style>
</head>
<body>
<div class="zwbox"></div>
<div class="container"> </div>
<script>
// 准备基础内容
const eltotal = document.createDocumentFragment()
for (let i = 0; i < 40; i++) {
let el = document.createElement('img')
el.src='https://tva3.sinaimg.cn/large/006APoFYly1g8mf9qe5udg30jz0jzjtr.gif'
el.className='spanel'
eltotal.append(el)
}
document.querySelector('.container').append(eltotal)
const elArr = document.querySelectorAll('.spanel')
/**
* 方案一 用事件监听的方式(缺点,事件高频触发导致性能不好)
* 这种方式在首屏时因为没有滚动触发不了事件,首屏的图片也不会加载,解决办法就是开局手动调用一次
*/
/**
window.addEventListener('scroll',()=>{
elArr.forEach(el=> {
// if(isInViewPortOfOne(el)){
if(isInViewPort(el)){
el.src='https://img2.baidu.com/it/u=861863691,2776527252&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500'
}
})
})
*/
// 方法二 观察者模式
function getYellow(entries, observer) {
entries.forEach(entry => {
// 判断元素是否在可视区域,
if(entry.isIntersecting){
// setTimeout(()=>{
entry.target.src='https://img2.baidu.com/it/u=861863691,2776527252&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500'
// },500)
// 如果只触发一次的话在触发完成后就取消当前的el监听,提高性能
observer.unobserve(entry.target)
}
});
}
const observer = new IntersectionObserver(getYellow, options);
elArr.forEach(el=> observer.observe(el))
</script>
</body>
</html>
图片预加载
/**
* 图片预加载
* 先说使用场景吧。当一个站点中某些页面的图片比较大时,会出现图片短暂的不显示,或者一部分一部分的显示出来。体验不是很好
* 那么此时就可以用预加载来处理
* 还是先上代码在讲道理 用上面图片懒加载来做案例
* */
function getYellow(entries, observer) {
entries.forEach(entry => {
if(entry.isIntersecting){
// 假设现在这张图片很大
let src = 'https://img2.baidu.com/it/u=861863691,2776527252&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500'
const image = new Image()
image.src = src
// 监听图片加载完成
image.onload = function (){
// 因为http get请求浏览器有缓存机制,当前这个地址的图片已经加载过,下一次使用就会直接取缓存
entry.target.src=src
observer.unobserve(entry.target)
}
}
});
}
const observer = new IntersectionObserver(getYellow, options);
elArr.forEach(el=> observer.observe(el))
- 如果哪里说的不对欢迎来喷我,老铁点赞不迷路