小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
图片延迟加载的3种实现方案与瀑布流的实现
- 基于JS 盒模型实现的懒加载方案
<div class="container clearfix">
<div class="column">
<!-- <div class="card">
<a href="#">
<div class="lazyImageBox">
<img src="" alt="" data-img="images/1.jpg">
</div>
<p>泰勒·斯威夫特(Taylor Swift),1989年12月13日出生于美国宾州,美国歌手、演员。2006年出道,同年发行专辑《泰勒·斯威夫特》,该专辑获得美国唱片业协会的白金唱片认证</p>
</a>
</div> -->
</div>
<div class="column"></div>
<div class="column"></div>
</div>
<div class="loadingBox"></div>
<!-- IMPORT JS -->
<script src="lib/utils.js"></script>
<script src="index.js"></script>
let imageModule = (function(){
let columns = Array.from(document.querySelectorAll('.column')),lazyImageBoxs
// 数据绑定
const bindHTML = function bindHTML(data){
//计算真实渲染的高度
data = data.map(item => {
let w = item.width, h = item.height;
h = h / ( w/230 )
item.width = 230
item.height = h
return item;
})
// 按照三个为一组进行循环(最后一组可能不到三项)
for(let i=0; i<data.length; i += 3){
// 把三组数据按照高度排序(升序)
let group = data.slice(i,i+3);
group.sort((a,b) => a.height - b.height)
// 把三个列按照现在的高度进行排序(降序)
columns.sort((a,b) => b.offsetHeight - a.offsetHeight)
// 循环三个数据中的每一项:每循环一项,创建一个CARD,把创建的CARD放到对应的列中即可
group.forEach((item, index) => {
let {pic, link, title, height,} = item
let card = document.createElement('div');
card.className = "card";
card.innerHTML = `<a href="${link}">
<div class="lazyImageBox" style="height:${height}px">
<img src="" alt="" data-img="${pic}">
</div>
<p>${title}</p>
</a>`;
columns[index].appendChild(card);
})
}
// 获取所有需要延迟加载的盒子
lazyImageBoxs = Array.from(document.querySelectorAll('.lazyImageBox'));
}
// 单张图片加载「为了防止加载的地址有错误,我们都是临时创建一个IMG去加载地址,能加载成功,才把地址赋值给真正的IMG」
const singleImgLoading = function singleImgLoading(imgBox){
let img = imgBox.querySelector('img'),
trueImg = img.getAttribute('data-img'),
tempImg = new Image();
tempImg.src = trueImg
tempImg.onload = function(){
img.src = trueImg;
img.style.opacity = 1;
tempImg = null
}
imgBox.isLoad = true;
}
// 根据条件规则,控制哪些延迟加载
const lazyImgsFunc = function lazyImgsFunc () {
lazyImageBoxs.forEach(imgBox => {
if(imgBox.isLoad) return //处理过就不在处理
//方案一
let A = imgBox.offsetHeight + utils.offset(imgBox).top,
B = document.documentElement.clientHeight + document.documentElement.scrollTop
if(A <= B) singleImgLoading(imgBox) //盒子完全出现在视口中
})
}
return{
// 模块入口:管控执行的逻辑
async init(){
let data = await utils.ajax('./data.json')
bindHTML(data);
setTimeout(lazyImgsFunc);
window.addEventListener('scroll', utils.throttle(lazyImgsFunc, 500));
}
}
})()
imageModule.init();
- 基于getBoundingClientRect的进阶方案
// 改变lazyImgFunc函数中的条件
// 解决方案二:getBoundingClientRect
let A = imgBox.getBoundingClientRect().bottom,
B = document.documentElement.clientHeight;
if (A <= B) singleImgLoading(imgBox);
- 终极方案:IntersectionObserver
let imageModule = (function(){
let columns = Array.from(document.querySelectorAll('.column')),
loadingBox = document.querySelector('.loadingBox'),
lazyImageBoxs
// 创建一个监听器:处理图片的延迟加载
let config = {
threshold:[1] //0 一露头 0.5 一半 1完全出现
}
let ob = new IntersectionObserver(changes => {
changes.forEach(item => {
let {
isIntersecting,//完全出现在视口中会变为true
target
} = item;
if(isIntersecting){
// 完全出现在视口中
singleImgLoading(target);
// 移除对当前盒子的监听
ob.unobserve(target)
}
})
},config)
// 监听到达底部
const watchBottom = function watchBottom() {
let obLoading = new IntersectionObserver(async changes => {
let item = changes[0];
if (item.isIntersecting) {
// 到底部了:加载更多的数据
let data = await utils.ajax('./data.json');
bindHTML(data);
lazyImgsFunc();
}
});
obLoading.observe(loadingBox);
};
// 数据绑定
const bindHTML = function bindHTML(data){
//计算真实渲染的高度
data = data.map(item => {
let w = item.width, h = item.height;
h = h / ( w/230 )
item.width = 230
item.height = h
return item;
})
// 按照三个为一组进行循环(最后一组可能不到三项)
for(let i=0; i<data.length; i += 3){
// 把三组数据按照高度排序(升序)
let group = data.slice(i,i+3);
group.sort((a,b) => a.height - b.height)
// 把三个列按照现在的高度进行排序(降序)
columns.sort((a,b) => b.offsetHeight - a.offsetHeight)
// 循环三个数据中的每一项:每循环一项,创建一个CARD,把创建的CARD放到对应的列中即可
group.forEach((item, index) => {
let {pic, link, title, height,} = item
let card = document.createElement('div');
card.className = "card";
card.innerHTML = `<a href="${link}">
<div class="lazyImageBox" style="height:${height}px">
<img src="" alt="" data-img="${pic}">
</div>
<p>${title}</p>
</a>`;
columns[index].appendChild(card);
})
}
// 获取所有需要延迟加载的盒子
lazyImageBoxs = Array.from(document.querySelectorAll('.lazyImageBox'));
}
// 单张图片加载「为了防止加载的地址有错误,我们都是临时创建一个IMG去加载地址,能加载成功,才把地址赋值给真正的IMG」
const singleImgLoading = function singleImgLoading(imgBox){
let img = imgBox.querySelector('img'),
trueImg = img.getAttribute('data-img'),
tempImg = new Image();
tempImg.src = trueImg
tempImg.onload = function(){
img.src = trueImg;
img.style.opacity = 1;
tempImg = null
}
imgBox.isLoad = true;
}
// 根据条件规则,控制哪些延迟加载
const lazyImgsFunc = function lazyImgsFunc () {
lazyImageBoxs.filter(imgBox => imgBox.getAttribute('isWatch') !== "TRUE"
).forEach(imgBox => {
ob.observe(imgBox)
imgBox.setAttribute('isWatch', 'TRUE')
// if(imgBox.isLoad) return //处理过就不在处理
// let A = imgBox.getBoundingClientRect().bottom,
// B = document.documentElement.clientHeight;
// if (A <= B) singleImgLoading(imgBox);
//方案一
// let A = imgBox.offsetHeight + utils.offset(imgBox).top,
// B = document.documentElement.clientHeight + document.documentElement.scrollTop
// if(A <= B) singleImgLoading(imgBox) //盒子完全出现在视口中
})
}
return{
// 模块入口:管控执行的逻辑
async init(){
let data = await utils.ajax('./data.json')
bindHTML(data);
lazyImgsFunc()
watchBottom()
}
}
})()
imageModule.init();
- 效果图