前言
图片延迟加载:真实项目中非常重要的一个性能优化手段;
- 在没有加载图片之前,图片区域用默认的背景图(背景色)占位即可
- 首屏:先加载首屏的其他数据(内容,样式,文本等),等待其它数据加载完成,再去加载真实的图片
- 其他屏:当我们滚动到指定区域(一露面,出来一半,完全出现在屏幕中),在去加载真实图片
onscroll特点:
只要页面滚动就会触发 < 在浏览器最快反应时间内(谷歌4 - 6ms), 触发一次, 触发频率特别频繁 >, 重复加载a <= b的这个条件, 只要页面一直向上滚动, 在第一次符合条件后, 每次滚动条件也是符合的, 这样 lazyImage方法会被执行多次(也就是延迟加载会被处理多次)
处理频率太快,我们需要降低他的频率 函数的节流throttle
意义: 如果不做延迟加载,那么页面渲染的时候,同时也要把资源请求回来,并且进行渲染,这样会阻碍页面的渲染进度,导致首次加载页面变慢,延迟加载一方面,可以提高页面加载速度,另外一方面也可以减少用户没必要的网路消耗
单张图片延迟加载 方案一
需要延迟加载的图片放置在一个盒子中,
盒子有一个默认占位的背景图,在没有加载真实图片之前,我们给用户呈现的是一个默认的占位图
img一开始不能设置src,因为器一但设置src页面加载的时候,就会加载图片了,达不到延迟加载的效果
把真实加载的图片地址放到image的lazy-image自定义属性上(自定义属性名自己设定即可)
注意: 开始图片是隐藏的:在某些IE浏览器中,如果图片的SRC是空的,或者加载的图片是错误的,图片不隐藏,会显示一个 X 的效果,很难看,所以图片没有加载真实地址,并且加载正确之前,还是让图片隐藏吧!
- 办法一:display: none; ->这种办法在加载完成真实图片后,还需要让其display: block;这样触发了DOM的回流重汇,性能消耗比较大...
- 办法二:opacity: 0; transition: opacity .3s; ->推荐这种方案,一方面后期加载真实图片后,我们只需要设置opacity: 1;,这样一方面不会引发DOM的回流重绘,一方面还可以CSS3过度动画实现出渐现的效果...
//节流函数
function throttle(func, wait = 500) {
let timer = null,
previous = 0;
return function anonymous(...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(() => {
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(this, ...params);
}, remaining);
}
};
}
//盒子顶部距离body上偏移
function offset(element) {
let l = element.offsetLeft,
t = element.offsetTop,
p = element.offsetParent;
while (p && p.tagName !== 'body') {
if (!/MSIE 8/.test(navigator.userAgent)) {
l += p.clientLeft;
t += p.clientTop;
}
l += p.offsetLeft;
t += p.offsetTop;
p = p.offsetParent;
}
return {
top: t,
left: l
};
}
//获取占位盒子 && html
let imageLazyBox = document.querySelector('.imageLazyBox'),
html = document.documentElement;
//执行lazyImage方法,传递需要延迟加载的盒子,实现单张图片延迟加载
function lazyImage(imageLazyBox) {
// 获取图片
let imageItem = imageLazyBox.querySelector('img'),
// 获取图片的自定义属性
lazy_image = imageItem.getAttribute('lazy-image');
// if (lazy_image === null) return;//性能不好
imageItem.onload = function () {
//当图片加载成功后触发的触发 onload vs onerror
imageItem.style.opacity = 1;
};
//图片的地址赋值给image的src
imageItem.src = lazy_image;
// 也有在不论图片是否加载成功,只要处理过,则就把lazy-image属性移除掉(证明其已经处理过了)
imageItem.removeAttribute('lazy-image');
//第一次执行这个方法,我们设置一个自定义属性isload = true 证明当前图片已经看过了
imageLazyBox.isload = true;
}
//监听滚动条图片全部显示后加载图片
window.onscroll = throttle(function () {
console.log('ok');
if (imageLazyBox.isload) return;
// 盒子底部距离body的距离:盒子顶部的距离+盒子本身高度
let a = offset(imageLazyBox).top + imageLazyBox.offsetHeight,
// 浏览器底边距离body: 一屏的高度-滚动条卷去的高度
b = html.clientHeight + html.scrollTop;
if (a <= b) {
lazyImage(imageLazyBox)
}
})
//设定定时器,延迟多久之后进行加载
// setTimeout(lazyImage, 1000);
// 或者触发条件:页面中所有资源都加载完成
// window.onload = function () { };
思路: //在浏览器滚动条滚动(页面滚动)过程中,等待图片完全出现在可视窗口内的时候,我们加载真实图片 "这是非首屏图片的处理步骤"-->计算出盒子底边距离body(a)的距离 和 浏览器底边距离body(b)的距离 做比较如果a<=b的时候加载真实图片即可
方案二
思路:基于getBoundingClientRect来判断图片的加载条件,不用自己写offset方法了
- a:获取当前盒子距离浏览器可视窗口的位置信息
- b可视窗口一屏的的大小
- 做比较如果a<=b的时候加载真实图片即可
function throttle(func, wait = 500) {
let timer = null,
previous = 0;
return function anonymous(...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(() => {
clearTimeout(timer);
timer = null;
previous = new Date();
func.call(this, ...params);
}, remaining);
}
};
}
let imageLazyBox = document.querySelector('.imageLazyBox'),
html = document.documentElement;
function lazyImage(imageLazyBox) {
let imageItem = imageLazyBox.querySelector('img'),
lazy_image = imageItem.getAttribute('lazy-image');
imageItem.onload = function () {
imageItem.style.opacity = 1;
};
imageItem.src = lazy_image;
imageItem.removeAttribute('lazy-image');
imageLazyBox.isload = true;
}
window.onscroll = throttle(function () {
//获取当前盒子距离浏览器可视窗口的位置信息
let a = imageLazyBox.getBoundingClientRect().bottom,
// 可视窗口一屏的的大小
b = html.clientHeight;
if (imageLazyBox.isload) return;
if (a <= b) {
lazyImage(imageLazyBox)
}
})
方案三
基于 IntersectionObserver实现图片延迟加载
设置一个监听器,后期可以监听指定的dom元素(可以多个),当监听的元素dom元素与可视窗口交叉位置发生改变,触发回调函数执行,-->changes是一个集合,集合中存储了一个dom元素与可视窗口交叉的状态信息
Observer.observe(imageLazyBox)监听dom元素
Observer.unobserve(imageLazyBox):解除监听
默认:第一次监听dom触发一次页面刚开始出现在页面中触发一次,元素消失在页面中触发一次
基于threshold配置项,控制触发监听条件,(0刚出现, 0.5出现一半, 1完全出现)
- IntersectionObserverEntry 每一个dom元素与可视窗口交叉的信息
- boundingClientRect获取就是基于imageLazyBox.getBoundingClientRect()获取的结果
- target:监听的dom元素
- isIntersecting:是否与可视窗口交叉了(布尔),如果为true说明元素出现在可视窗口中,反之如果为false说明不在可视窗口中
let imageLazyBox = document.querySelector('.imageLazyBox'),
//配置项只有完全触发出现
options = {
threshold: [1]
};
function lazyImage(imageLazyBox) {
let imageItem = imageLazyBox.querySelector('img'),
lazy_image = imageItem.getAttribute('lazy-image');
imageItem.onload = function () {
imageItem.style.opacity = 1;
};
imageItem.src = lazy_image;
imageItem.removeAttribute('lazy-image');
imageLazyBox.isload = true;
}
let ob = new IntersectionObserver(change => {
// item监听的dom元素与可视窗口的交叉信息
// target存储当前监听的那个盒子
let item = change[0],
target = item.target;
if (item.isIntersecting) {
//符合条件:盒子已经完全出现在视口中
lazyImage(target);
// 处理过一次延迟加载,以后当前这个元素无需在次监听处理了
ob.unobserve(imageLazyBox)
}
}, options)
//监听dom元素
ob.observe(imageLazyBox)
基于IntersectionObserver,实现延迟加载,相对于之前 window.onscroll实现延迟加载的优势
- 优势:更方便 ,onscrol方法中需要自己编写throttle函数进行节流处理,以此优化性能;但是IntersectionObserver内置节流处理,而且性能上比onscroll也好很多!!
- 弊端:兼容性不好,IE中只有EDGE新版本兼容,其余都不兼容,火狐谷歌等也得是 >=60 左右的新版本才兼容,所以移动端开发目前使用的比较多
Can I use... Support tables for HTML5, CSS3, etc测试方法兼容性的
方案四
loading="lazy" 浏览器会自己计算该何时加载,帮助我们加载真实的图片
<img src="images/1.jpg" loading="lazy">