原生JS - 图片懒加载@郝晨光
前言
在现在以用户体验至上的前端时代,为了提升页面加载速度,为了提升用户体验,我们经常会用到图片懒加载这个功能。所以总结记录一下图片懒加载的原理以及实现方式
![](https://img.haomeiwen.com/i18087456/d7c6d20cdf006288.jpg)
什么是懒加载?
懒加载其实就是延迟加载,是一种对网页的性能优化的方式,当访问一个页面的时候,优先显示可视区域内的图片,而不是一次性加载所有图片,当需要显示的时候,再进行加载,避免打开网页时加载过多的资源,发送过多的HTTP请求。
实现原理
- <img>标签的src属性就是代表了一个资源引用,它的src就代表了图片资源的路径,如果这个属性不为空,浏览器就会发送请求,如果为空的话就不发送。
- 我们就可以利用这一点,在需要的时候,我们再给图片设置src属性。
- 其实图片懒加载的原理,就是通过动态的判断图片是否处在可视区域内,如果处在可视区域内的话,就加载当前图片,否则的话,就先不加载。
- 我们需要监听浏览器的scroll事件,并在滑动的时候进行判断,当所有图片都加载完成以后,应该清除监听事件。
- 对于scroll事件,我们需要实现函数节流的,避免函数重复触发,减少浏览器负荷,提升用户体验。
具体实现
HTML实现
<ul>
<li>
<img data-src="./Image%203.png" alt="">
</li>
<li>
<img data-src="./Image%203.png" alt="">
</li>
<li>
<img data-src="./Image%203.png" alt="">
</li>
<li>
<img data-src="./Image%203.png" alt="">
</li>
<li>
<img data-src="./Image%203.png" alt="">
</li>
<li>
<img data-src="./Image%203.png" alt="">
</li>
</ul>
Javascript实现(第一种方式)
写在前边说一下javascript中的getBoundingClientRect()这个方法
这个方法可以获取到DOM节点距离四周的距离,包含四个属性:top、left、bottom、right
,分别代表的是:元素的上边框距离页面顶部的距离、元素的左边框距离页面左边的距离、元素的下边框距离页面底部的距离、元素的右边框距离页面右边的距离。
/**
* @function throttle 函数节流
* @param fn 需要函数节流执行的函数
* @param interval 函数触发的间隔时间
* */
function throttle(fn, interval) {
// timer 定时器,
// firstTime判断是否是第一次执行,第一次执行不需要节流
let timer = null, firstTime = true;
// 闭包
return function () {
// args 保存原有参数,
// _this保存原有this
let args = arguments, _this = this;
// 如果是第一次执行
if (firstTime) {
// 直接调用函数
fn.apply(_this, args);
// 并修改firstTime,结束本次函数调用
return firstTime = false;
}
// 如果当前存在定时器,则直接返回,不能继续运行
if (timer) {
return false;
}
// 如果不存在定时器,进行到这一步
timer = setTimeout(function () {
// 先清除之前的定时器
clearTimeout(timer);
// 初始化 timer 变量
timer = null;
// 调用函数
fn.apply(_this, args);
}, interval || 500)
}
};
/**
* @function inInSight 判断当前元素是否在可视区域内
* @param {HTMLElement} el img图片元素
* @return {Boolean} 返回布尔值,true表示处在可视区域内
*
*/
function inInSight(el) {
let info = el.getBoundingClientRect();
let client = window.innerHeight;
return info.top <= client - 200;
}
let imgs = document.querySelectorAll('img');
let imgArr = Array.prototype.slice.call(imgs);
// 本文由郝晨光总结并编写,未经允许禁止转载。
/**
* @function imgLoad 加载图片 通过遍历判断页面上所有的图片,将处在视野内的图片加载到页面上
*/
function imgLoad() {
// 遍历未加载的图片
for (let index = 0; index < imgArr.length; index++) {
// 判断当前图片是否处在视野内
if (inInSight(imgArr[index])) {
// 将处在视野内的图片的data-src赋予src属性,则开始加载该图片
imgArr[index].src = imgArr[index].dataset.src;
// 在数组内清除该图片
imgArr.splice(index, 1);
// index--,继续遍历
index--;
}
}
// 当图片全部加载完成时,取消监听事件
if (imgArr.length === 0) {
window.removeEventListener('scroll', throttleImgLoad);
}
}
// 节流imgLoad方法,200毫秒触发一次
const throttleImgLoad = throttle(imgLoad, 200);
// 页面初始化调用一次
imgLoad();
// 监听scroll事件,触发懒加载函数
window.addEventListener('scroll', throttleImgLoad);
实现效果
为了看得更清楚一些,我在图片的位置加了一个边框,并且给图片设置了固定的宽高。
![](https://img.haomeiwen.com/i18087456/ec5c3f0e77b73e4b.gif)
Javascript实现(第二种方式)
写在前边:在第二种方式中,我们使用了 IntersectionObserver 这个API,IntersectionObserver可以自动观察元素是否在视口内。
var io = new IntersectionObserver(callback, option);
// 开始观察
io.observe(document.getElementById('example'));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();
callback回调函数,参数是一个数组,每个数组都是一个 IntersectionObserverEntry对象,包括以下属性:
属性 | 描述 |
---|---|
time | 可见性发生变化的时间,单位为毫秒 |
rootBounds | 与getBoundingClientRect()方法的返回值一样 |
boundingClientRect | 目标元素的矩形区域的信息 |
intersectionRect | 目标元素与视口(或根元素)的交叉区域的信息 |
intersectionRatio | 目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0 |
target | 被观察的目标元素,是一个 DOM 节点对象 |
我们需要用到 intersectionRatio来判断是否在可视区域内,当intersectionRatio>0&&intersectionRatio<=1即在可视区域内。然后通过target拿到DOM节点,并给它设置src。
let imgs = document.querySelectorAll('img');
let imgArr = Array.prototype.slice.call(imgs);
const io = new IntersectionObserver(ioes => {
ioes.forEach(ioe => {
const el = ioe.target;
const intersectionRatio = ioe.intersectionRatio;
if(intersectionRatio > 0 && intersectionRatio <= 1) {
el.src = el.dataset.src;
}
el.onload = el.onerror = () => io.unobserve(el);
})
});
for(let i=0;i<imgArr.length;i++) {
io.observe(imgArr[i])
}
// 本文由郝晨光总结并编写,未经允许禁止转载。
实现效果
由于这种方法实现的效果过于流畅,所以我换一种方式让大家看这个懒加载的效果,请看右边的Network
![](https://img.haomeiwen.com/i18087456/a5ec8f6d0ee281b8.gif)
如果本文对您有帮助,可以看看本人的其他文章:
Koa - Node.js框架学习@郝晨光
前端常见面试题(十二)@郝晨光
前端常见面试题(十一)@郝晨光