图片懒加载的意义
在项目中,如果图片数量很大的情况下,如果直接都加载渲染出来,会给网络请求和浏览器渲染性能造成比较大的压力,导致首页白屏时间较长。这是图片懒加载就起到了关键的优化作用。
实现
首次渲染的时候,不渲染真实图片,而是给图片位置设置一个默认的背景图占位,比如下面的代码,一开始加载的时候,给img标签设置一个默认占位图,把真实的图片地址设置到另一个属性上,这样,当需要展示真实图片的时候,js动态替换src路径就可以了。
html
<div class="lazyImageBox">
<img src="default.jpg" alt="" lazy-image="images/12.jpg">
</div>
css
html,
body {
height: 300%;
}
.lazyImageBox {
position: absolute;
left: 50%;
top: 1500px;
transform: translateX(-50%);
width: 400px;
height: 300px;
/* 设置背景图占位可以有渐变效果 */
background: url("./images/default.gif") no-repeat center center #EEE;
}
.lazyImageBox img {
width: 100%;
height: 100%;
opacity: 0;
transition: opacity .3s;
}
实现懒加载
单张图片懒加载
- 方法一 使用IntersectionObserver原生API(ie不兼容)
// 图片容器的dom
const lazyImageBox = document.querySelector('.lazyImageBox');
// 图片标签dom
const img = lazyImageBox.querySelector('img');
const io = new IntersectionObserver(([IntersectionObserverEntry]) => {
const { isIntersecting } = IntersectionObserverEntry;
if (isIntersecting) {
const realSrc = img.getAttribute('lazy-image')
img.src = realSrc;
img.onload = () => {
img.style.opacity = 1;
};
// 取消监听
io.unobserve(lazyImageBox);
}
}, {
// 取值范围[0-1],图片展示出来的比例
// 取0时,刚露出isIntersecting就会变为true
// 取1时,完全露出isIntersecting才会变为true
threshold: [0.5]
})
// 监听图片容器是否显示在屏幕中
io.observe(lazyImageBox)
- 方法二
// 节流函数
function throttle(func, wait = 500) {
let timer = null,
previous = 0;
return function (...params) {
let now = new Date(),
remaining = wait - (now - previous);
if (remaining <= 0) {
clearTimeout(timer);
timer = null;
previous = now;
func.call(this, ...params);
} else if (!timer) {
timer = setTimeout(() => {
timer = null;
previous = new Date();
func.call(this, ...params);
}, remaining);
}
};
}
// 获取盒子容器
const lazyImageBox = document.querySelector('.lazyImageBox');
// 获取img图片
const lazyImage = lazyImageBox.querySelector('img');
// 加载真实图片
const singleLazy = function () {
let trueImg = lazyImage.getAttribute('lazy-image');
lazyImage.src = trueImg;
lazyImage.onload = () => {
// 真实图片加载成功
lazyImage.style.opacity = 1;
};
// 表示该图片已经被加载过了
lazyImageBox.isLoad = true;
};
const lazyFunc = function () {
// 防止重复处理
if (lazyImageBox.isLoad) return;
// 图片容器底部到视口顶部的距离
const A = lazyImageBox.getBoundingClientRect().bottom;
// 视口高度
const B = document.documentElement.clientHeight;
// 容器完全出现在视口(图片容器底部到视口顶部的距离等于是视口高度)
if (A <= B) {
// 加载真实图片
singleLazy();
}
};
// 浏览器会默认触发一次lazyFunc,然后滚动条滚动时触发(节流处理)
window.onscroll = throttle(lazyFunc);
多张图片延迟加载插件
class LazyImgPlugin {
constructor({
context = document,
attr = 'lazy-image',
callback = Function.prototype,
speed = 300,
threshold = 0
}) {
this.context = context;
this.attr = attr;
this.speed = speed;
this.callback = callback;
this.threshold = threshold;
this.imageBoxesList = [];
this.ob = null;
this.init();
}
init() {
this.ob = new IntersectionObserver(changes => {
// 遍历所有监听的目标
changes.forEach(change => {
const {
// 是否在屏幕内
isIntersecting,
// 监听的dom元素
target
} = change;
if (isIntersecting) {
// 需要加载target
this.lazySingle(target);
// 移除对target元素的监听
this.ob.unobserve(target);
}
});
}, {
threshold: [this.threshold]
});
this.observerAll();
}
// 加载目标图片
lazySingle(imageBox) {
const img = imageBox.querySelector('img');
const realSrc = img.getAttribute(this.attr);
img.src = realSrc;
img.onload = () => {
img.style.transition = `opacity ${this.speed}ms`;
img.style.opacity = 1;
img.removeAttribute(this.attr);
this.callback.call(this, imageBox);
};
}
observerAll(isRefresh) {
// 找到所有带lazy-image属性的img元素
const imgs = this.context.querySelectorAll(`img[${this.attr}]`);
imgs.forEach(img => {
// 找到每一个img的父节点
const imgBox = img.parentNode;
// 解决快速下拉出现的问题
if (isRefresh && this.imageBoxesList.includes(imgBox)) return;
// 监听这个父节点的出现
this.ob.observe(imgBox);
this.imageBoxesList.push(imgBox);
});
// 删除无效元素
this.imageBoxesList = this.imageBoxesList.filter(image => image.querySelector('img').getAttribute(this.attr));
}
refresh() {
// 重新监听
this.observerAll(true);
}
}