js实现图片懒加载
一、 核心思想
先将img标签中的src链接设为同一张图片,
真正的图片地址存储在img标签的自定义属性中。
当js监听到该图片元素进入可视窗口时,将自定义属性中的地址存储到src属性中。
二、具体实现
0.HTML部分
<div class="wrapper" id="box"></div>
1.创建要懒加载的元素并设置默认加载图片
// 先把这些资源的放在一个数组,需要的时候加载
var imgarr = [
'http://pic1.win4000.com/wallpaper/1/54977e4981369.jpg',
'http://image2.xyzs.com/upload/78/e1/110709/20130831/137796023651298_0.jpg',
'http://image1.xyzs.com/upload/c6/f3/111382/20130831/137796023398917_0.jpg',
'http://image3.xyzs.com/upload/a2/b4/112369/20130831/137796022421499_0.jpg',
];
var defaulUrl = 'https://5b0988e595225.cdn.sohucs.com/images/20191114/40514a41531044e985cee02793daf2bb.jpeg';
// 获取容器
var box = document.getElementById('box');
// 创建要懒加载的元素并设置默认加载图片
/*
url: 图片的地址
type: 要创建的元素类型
defaulUrl: 默认图片
target:盒子,将创建的元素插入其中
*/
var createElement = function (url, type, defaulUrl, target) {
// 兼容处理
// 必传参数
if (!url) throw new Error("the first arguments is not give");
if (!type) throw new Error("the second arguments is not give");
// 非必传参数
var target = target || document.body; //默认参数
var calculator = {
img: function () {
var img = new Image();
img.alt = '加载失败啦';
return img;
}
}
// 多态,参数类型判断
if (typeof url == 'string') {
createDom(url);
} else {
// 若是数组
url.forEach(function (item, index) {
createDom(item);
});
}
function createDom(url) {
var dom = calculator[type]();
dom.src = defaulUrl;
dom.dataset.src = url;
target.appendChild(dom);
}
}
2.页面解析完加载元素
window.onload = function () {
// 创建元素
createElement(imgarr, 'img', defaulUrl, box);
// 页面加载完先执行一次
lazyLoading();
}
3.懒加载方法
(1)使用offsetTop - scrollTop < clientHeight;优化使用节流
var lazyLoading = function () {
// 获取元素
var imgs = [].slice.call(document.querySelectorAll('img'));
var clientHeight = document.documentElement.clientHeight; //固定的值 可视区高度
var scrollTop = document.documentElement.scrollTop;//变化的值 滚动条滚动的距离
imgs.forEach(function (img, i) {
var offsetTop = img.offsetTop; //固定的值 相对于文档左上角
if (offsetTop - scrollTop < clientHeight) {
img.src = img.dataset.src;
}
})
}
(2)使用getBoundingClientRect();优化使用节流
var lazyLoading = function () {
// 获取元素
var imgs = [].slice.call(document.querySelectorAll('img'));
var clientHeight = document.documentElement.clientHeight;
imgs.forEach(function (img, i) {
// 判断该图片是否能够加载----也就是判断图片是否在可视区域viewport
// 每个dom对象都有一个函数 getBoundingClientRect()
// getBoundingClientRect用于获取某个元素相对于视窗的位置集合。集合中有top, right, bottom, left等属性。
var rect = img.getBoundingClientRect();
if (rect.bottom <= 0) {
return false;
}
if (rect.top >= clientHeight) {
return false;
}
//加载图片
img.src = img.dataset.src;//自定义data-src属性的获取
})
}
(3)使用IntersectionObserver API ,不需要使用节流
//参考阮一峰博客:http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html
var lazyLoading = function () {
var imgObserver = new IntersectionObserver(function (entrys) {
entrys.forEach(function (entry) {
// 可见元素entry.intersectionRatio > 0
if (entry.intersectionRatio > 0) {
let img = entry.target;
img.src = img.dataset.src;
imgObserver.unobserve(img);
}
})
});
// 获取元素
var imgs = [].slice.call(document.querySelectorAll('img'));
imgs.forEach(function (img) {
imgObserver.observe(img);
})
}
三、节流
1. 对方法(1)和方法(2)的优化
// 触发执行 缺点:触发频繁太耗性能
// document.addEventListener('scroll', lazyLoading);
// 改进: 使用节流:将多次执行变成每隔一段时间执行。
/**
* 函数节流方法
* @param Function fn 延时调用函数
* @param Number delay 延迟多长时间
* @param Number atleast 至少多长时间触发一次
* @return Function 延迟执行的方法
*/
var throttle = function (fn, delay, atleast) {
var timer = null; //闭包的体现 ,相当于是*相对的全局变量*哦。
var previous = null;
return function () {
var now = +new Date();
if (!previous) previous = now;
// 逻辑流程走的是“至少触发一次”的逻辑
if (atleast && now - previous > atleast) {
fn();
// 重置上一次开始时间为本次结束时间
previous = now;
clearTimeout(timer);
} else {
//timeout 的延迟逻辑
clearTimeout(timer);
timer = setTimeout(function () {
fn();
previous = null;
}, delay);
}
}
};
window.onscroll = throttle(lazyLoading, 500, 1000);
2.补充节流和防抖的应用场景
四、封装jQuery插件
//封装的插件
$.fn.extend({
imgLazyLoading: function () {
// console.log('我是图片懒加载');
// console.log(this); //jQuery对象
var imgObserver = new IntersectionObserver(function (entrys) {
entrys.forEach(function (entry) {
// 可见元素entry.intersectionRatio > 0
if (entry.intersectionRatio > 0) {
let img = entry.target;
img.src = img.dataset.src;
imgObserver.unobserve(img);
}
})
});
// 获取元素
var imgs = Array.from(this); //想办法把jQuery对象转为数组
imgs.forEach(function (img) {
imgObserver.observe(img);
});
}
})
// 这样使用
$(function () {
var imgs = $('img');
imgs.imgLazyLoading();
})