引言
图片懒加载是前端性能优化中老生常谈的话题了,核心思想就是控制视口外的图片进入视口时候再进行加载,从而提升页面初始化渲染性能和用户体验。最近发现又有Intersection Observer
这种新的API
来更好的判断元素位置,因此将图片懒加载的技术进行一次回顾总结。
图片懒加载实现
图片通常采用<img>
和background
两种形式来加载,那我们分别来演示两种情形的懒加载实现细节。
<img>
标签形式的懒加载
首先,<img>
标签通过src
属性的url
来加载图片,因此将需要懒加载的图片url
添加到data-src
属性,这样src
为空就不会加载图片了。
<img data-src="https://img.dpm.org.cn/Uploads/Picture/2021/05/31/s60b446b015652.jpg">
然后,需要判断条件来触发图片加载,目前有两种方法,传统的事件触发和最新的Intersection Observer
:
通过js
事件触发
需要注意的是有三种事件都可能导致图片的可视数量发生变化:scroll
,resize
和oritentionChange
。
document.addEventListener("scroll", lazyload);
window.addEventListener("resize", lazyload);
window.addEventListener("orientationChange", lazyload);
当任意一个事件触发我们就需要判断需要懒加载图片是否在视口内,最常用的判断方法就是getBoundingClientRect
:
function isInViewPort(element) {
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const {
top,
right,
bottom,
left,
} = element.getBoundingClientRect ();
// 这个是判断元素完整的在视口内
/* return (
top >= 0 &&
left >= 0 &&
right <= viewWidth &&
bottom <= viewHeight
); */
// 这个是判断元素刚进入视口
return (
top >= 0 &&
top <= viewHeight &&
left >= 0
);
}
判断图片如果到了视口内,就将data-src
的值赋给src
,最后判断是否所有需要懒加载的图片已经完成,再移除事件。
function lazyload() {
if (lazyloadThrottleTimeout) {
clearTimeout(lazyloadThrottleTimeout);
}
lazyloadThrottleTimeout = setTimeout(function() {
lazyloadImages.forEach(function(img) {
if (isInViewPort(img)) {
img.src = img.dataset.src;
img.classList.remove('lazy');
}
});
if (lazyloadImages.length == 0) {
document.removeEventListener("scroll", lazyload);
window.removeEventListener("resize", lazyload);
window.removeEventListener("orientationChange", lazyload);
}
}, 100);
}
注意这里加了个定时器防止事件高频触发,其实就是一个简单的防抖函数debounce
。完整代码见demo演示地址。
Intersection Observer
触发图片加载
Intersection Observer
是一个比较新的api,目前不支持IE,用他来检测图片是否进入视口非常方便,不用再像之前绑定事件、计算距离等。下面看实现吧:
if ("IntersectionObserver" in window) {
lazyloadImages = document.querySelectorAll(".lazy");
var imageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var image = entry.target;
image.src = image.dataset.src;
image.classList.remove("lazy");
imageObserver.unobserve(image);
}
});
});
lazyloadImages.forEach(function(image) {
imageObserver.observe(image);
});
}
将所有需要懒加载的图片注册observer
,用isIntersecting
属性监控,其他逻辑都一样的。完整代码见demo演示地址。
background
形式的懒加载
其实现在项目很少大面积的用img
标签了,背景图片的使用比较多,处理其实也很简单,就是用css
选择器来控制background-image
是否加载。有lazy
选择器时候background-image: none;
,元素进入视口后移除选择器,相应图片的地址就可以加载出来了。
html
:
<div class="img img-1"></div>
<div class="img img-2"></div>
<div class="img img-3 lazy"></div>
<div class="img img-4 lazy"></div>
<div class="img img-5 lazy"></div>
css
:
.img {
width: 400px;
height: 300px;
border: 1px solid #d9d9d9;
margin: 20px auto;
background-size: 100% 100%;
}
.img.lazy {
background-image: none;
}
.img-1 {
background-image: url(https://img.dpm.org.cn/Uploads/Picture/2021/05/31/s60b446b015652.jpg);
}
.img-2 {
background-image: url(https://img.dpm.org.cn/Uploads/Picture/2021/05/31/s60b445921bdfe.jpg);
}
.img-3 {
background-image: url(https://img.dpm.org.cn/Uploads/Picture/2021/05/31/s60b443ac90165.jpg);
}
.img-4 {
background-image: url(https://img.dpm.org.cn/Uploads/Picture/2021/05/31/s60b4430138464.jpg);
}
.img-5 {
background-image: url(https://img.dpm.org.cn/Uploads/Picture/2021/05/31/s60b441f616f18.jpg);
}
js
:
document.addEventListener("DOMContentLoaded", function() {
var lazyloadImages = document.querySelectorAll(".img.lazy");
var lazyloadThrottleTimeout;
function lazyload() {
if (lazyloadThrottleTimeout) {
clearTimeout(lazyloadThrottleTimeout);
}
lazyloadThrottleTimeout = setTimeout(function() {
lazyloadImages.forEach(function(img) {
if (isInViewPort(img)) {
img.classList.remove('lazy');
}
});
if (lazyloadImages.length == 0) {
document.removeEventListener("scroll", lazyload);
window.removeEventListener("resize", lazyload);
window.removeEventListener("orientationChange", lazyload);
}
}, 20);
}
function isInViewPort(element) {
const viewWidth = window.innerWidth || document.documentElement.clientWidth;
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
const {
top,
right,
bottom,
left,
} = element.getBoundingClientRect ();
// 这个是判断元素完整的在视口内
/* return (
top >= 0 &&
left >= 0 &&
right <= viewWidth &&
bottom <= viewHeight
); */
// 这个是判断元素刚进入视口
return (
top >= 0 &&
top <= viewHeight &&
left >= 0
);
}
document.addEventListener("scroll", lazyload);
window.addEventListener("resize", lazyload);
window.addEventListener("orientationChange", lazyload);
});
完整代码见demo演示地址。
总结
图片懒加载的基本原理很简单,后续还有很多优化的手段,包括占位图片的使用来提升用户体验,还有图片的压缩等,以后实践中慢慢补充。