目的
1、网页优化,提高网页加载速度
2、页面优化友好,提高SEO收录与排名
3、提高用户体验,减少服务器压力
原理
一张图片就是一个<img>标签,浏览器是否发起请求图片是根据<img>的src属性,所以实现懒加载的关键就是,在图片没有进入可视区域时,先不给<img>的src赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给src赋值。
效果预览
实现方式
图片懒加载的方式主要有两种:
1、利用 getBoundingClientRect API得到当前元素与视窗的距离来判断
2、利用h5的新API IntersectionObserver 来实现
一、getBoundingClientRect API
Element.getBoundingClientRect() 方法返回值是一个 DOMRect 对象,包含了该元素一组矩形的集合:是与该元素相关的css边框集合(top, left, right, bottom)。
代码如下:
html
<div class="container">
<header>头部</header>
<main id="main">
<div class="list">
<img
src="./images/default1.png"
data-src="./images/zs1.jpg"
alt=""
class="lazyload"
/>
</div>
<div class="list">
<img
src="./images/default1.png"
data-src="./images/zs2.jpg"
alt=""
class="lazyload"
/>
</div>
<div class="list">
<img
src="./images/default1.png"
data-src="./images/zs3.jpeg"
alt=""
class="lazyload"
/>
</div>
<div class="list">
<img
src="./images/default1.png"
data-src="./images/zs4.jpeg"
alt=""
class="lazyload"
/>
</div>
<div class="list">
<img
src="./images/default1.png"
data-src="./images/zs5.jpg"
alt=""
class="lazyload"
/>
</div>
<div class="list">
<img
src="./images/default1.png"
data-src="./images/zs6.jpeg"
alt=""
class="lazyload"
/>
</div>
<div class="list">
<img
src="./images/default1.png"
data-src="./images/zs7.jpeg"
alt=""
class="lazyload"
/>
</div>
<div class="list">
<img
src="./images/default1.png"
data-src="./images/zs8.jpeg"
alt=""
class="lazyload"
/>
</div>
</main>
<footer>底部</footer>
</div>
css:
<style>
body {
margin: 0;
padding: 0;
}
.container {
position: relative;
}
header,
footer {
position: fixed;
width: 100%;
height: 50px;
text-align: center;
line-height: 50px;
background: #eee;
color: #333;
z-index: 10;
}
header {
top: 0;
}
footer {
bottom: 0;
left: 0;
}
main {
width: 100%;
height: 100%;
position: absolute;
top: 50px;
bottom: 50px;
overflow-y: scroll; // 方法一此样式删除,方法二添加
}
.list {
width: 50%;
/* height: 300px; */
margin: 0 auto 20px;
text-align: center;
}
.list img {
max-width: 100%;
max-height: 100%;
}
</style>
js:
此写法来源于麻油,初次看到这篇文章,发现其写法简洁明了,立马mark下来一试,结果很不错呀,暂时没想到更简洁的写法,就记录下来以供学习。
// 图片懒加载类
class LazyLoad {
constructor(el) {
// 需使用懒加载的图片集合
this.imglist = Array.from(document.querySelectorAll(el));
this.init(); // 初始化
}
// 获取图片与窗口信息
getBound(el) {
let bound = el.getBoundingClientRect();
let clientHeight = window.innerHeight;
// 图片距离顶部的距离 <= 浏览器可视化的高度,从而推算出是否需要加载
return bound.top <= clientHeight - 50; // -50是头部的距离
}
// 加载图片
loadImage(el, index) {
let src = el.getAttribute("data-src");
el.src = src;
// 避免重复判断,已经确定加载的图片应当从imglist移除
this.imglist.splice(index, 1);
}
// 判断是否该图片可以加载
canLoad() {
let imglist = this.imglist;
for (let i = imglist.length; i--; ) {
this.getBound(imglist[i]) && this.loadImage(imglist[i], i);
}
}
// 当浏览器滚动的时候,继续判断
bindEvent() {
window.addEventListener("scroll", () => {
this.imglist.length && this.canLoad();
});
}
// 初始化
init() {
this.canLoad();
this.bindEvent();
}
}
// 实例化对象,参数则是需要使用懒加载的图片类名
const lazy = new LazyLoad(".lazyload");
二、IntersectionObserver
API介绍:
IntersectionObserver
IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗(viewport)交叉状态的方法。祖先元素与视窗(viewport)被称为根(root)。
当一个IntersectionObserver对象被创建时,其被配置为监听根中一段给定比例的可见区域。一旦IntersectionObserver被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;然而,你可以在同一个观察者对象中配置监听多个目标元素。
作为一个新型的API,会有一定的兼容性,查看兼容性
属性
root: 用于观察的根元素,默认是浏览器的视口,也可以指定具体元素,指定元素的时候用于观察的元素必须是指定元素的子元素
const options = {
root: document.querySelector('.box')
}
rootMargin: 用来扩大或者缩小视窗的的大小,使用css的定义方法,10px 10px 30px 20px表示top、right、bottom 和 left的值
thresholds: 用来指定交叉比例,决定什么时候触发回调函数,是一个数组,默认是[0]。
const options = {
root: null,
threshold: [0, 0.5, 1]
}
var io = new IntersectionObserver(callback, options)
io.observe(document.querySelector('img'))
上面代码,我们指定了交叉比例为0,0.5,1,当观察元素img0%、50%、100%时候就会触发回调函数
方法
IntersectionObserver.disconnect: 停止所有监听工作
IntersectionObserver.observe: 开始监听一个目标元素
IntersectionObserver.takeRecords: 返回所有观察目标对象的数组
IntersectionObserver.unobserve: 停止监听特定目标元素。
callback函数
当元素的可见性变化时,就会触发callback函数。
callback函数会触发两次,元素进入视窗(开始可见时)和元素离开视窗(开始不可见时)都会触发。
callback有个entries参数,它是个IntersectionObserverEntry对象数组,它的属性如下:
boundingClientRect 目标元素的矩形信息
intersectionRatio 相交区域和目标元素的比例值 intersectionRect/boundingClientRect 不可见时小于等于0
intersectionRect 目标元素和视窗(根)相交的矩形信息 可以称为相交区域
isIntersecting 目标元素当前是否可见 Boolean值 可见为true
rootBounds 根元素的矩形信息,没有指定根元素就是当前视窗的矩形信息
target 观察的目标元素
time 返回一个记录从IntersectionObserver的时间到交叉被触发的时间的时间戳
懒加载代码实现:
普通写法:
const options = {
root: document.querySelector(".main")
};
const io = new IntersectionObserver(callback, options);
let imgs = document.querySelectorAll(".lazyload");
function callback(entries) {
entries.forEach(item => {
if (item.isIntersecting) {
item.target.src = item.target.dataset.img;
io.unobserve(item.target);
}
});
}
imgs.forEach(item => {
io.observe(item);
});
es6函数封装
class LazyLoad {
constructor(config) {
this.imgs = []; // 观察节点集合
this.io = null; // 实例函数
// 默认参数
this.default = {
root: null
};
this.settings = { ...this.default, ...config };
this.init();
}
// 初始化
init() {
this.imgs = Array.from(document.querySelectorAll("[data-src]"));
this.getObserver(); // 实例化
this.addObserver(); // 开始观察
}
// 开始观察,观察节点
addObserver() {
this.imgs.forEach(item => {
this.io.observe(item);
});
}
// 实例化
getObserver() {
// 配置,指定可视区域,root默认为浏览器视口
let options = {
root: this.settings.root
};
console.log(this.settings.root);
this.io = new IntersectionObserver(entries => {
entries.forEach(item => {
// 当前元素可见
if (item.isIntersecting) {
this.loadImage(item.target); // 替换图片
this.io.unobserve(item.target); // 停止观察当前元素,避免不可见时候再次调用callback函数
}
});
}, options);
}
// 显示图片
loadImage(el) {
el.src = el.dataset.src;
}
}
const config = {
root: document.querySelector(".main")
};
// config可不传,不传root默认为浏览器视口
const lazyload = new LazyLoad(config);
后记
代码放在已更新到github
记录学习过程,好记性不如烂笔头~