额,算了 直接上代码,注释写的挺详细的没必要再介绍了--哈~~
直接复制打开就可以看到效果-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.waterfall {
width: 100vw;
display: flex;
flex-wrap: wrap;
position: relative;
}
.item {
width: 200px;
background-color: #ccc;
position: absolute;
vertical-align: top;
}
</style>
</head>
<body>
<!-- 瀑布流 指的是一种布局方式 有两种模式 确定宽度 确定列 在这写的是确定宽度-->
<!-- 瀑布流 + 图片懒加载 + 触底加载 -->
<div class="waterfall" id="container">
<!-- <img class="item"
data-src="https://ts3.cn.mm.bing.net/th?id=OIP-C.Rq5h9rKa8uBoUxtGTCCVRQHaKd&w=210&h=297&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2"
alt="" src=""> -->
</div>
<script>
/***
* 本案例主要做的是瀑布流布局(固定列宽) + 触底加载 + 图片懒加载 瀑布流懒加载不能结合
* 瀑布流 是对现有的进行布局
* 懒加载 是对不在视觉内的图片的操作
* 触底 是指页面数据加载完成并滚动到了页面底部要加载新数据
* 思路:
* 有个容器 开始搞
* imgs1 = [] 图片路径
* 思路:-- -- 可忽略
* --
* 触底很好做 就是这个懒加载 懒加载的前提 是要判断元素在不在页面中
* 我做一张图 图加载完 就布局一次 在做一张图 加载之前先看一看在不在页面中
* 陷入了悖论 灵感-后端传来的是图片源信息 src都设置完成了 才布局 只有布局之后才能判断要不要设置src (只能在有一个已经加载完了 这个不在页面内了 夏夜个肯定不在页面内 就停止设置src)
* --
*/
// 模拟后端传来的源信息 有宽高 20一组
let imgs1 = [
{path:'https://ts3.cn.mm.bing.net/th?id=OIP-C.Rq5h9rKa8uBoUxtGTCCVRQHaKd&w=210&h=297&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2', width:200, height:284},
{path:'https://ts3.cn.mm.bing.net/th?id=OIP-C.atTl-J5TF3GE5FWfRtTIygHaJ8&w=215&h=289&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2', width:200, height:270},
{path:'https://ts3.cn.mm.bing.net/th?id=OIP-C.QFdwl07_aviM1ch2KpyyFgHaEo&w=316&h=197&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2', width:200, height:125},
{path:'https://ts1.cn.mm.bing.net/th?id=OIP-C.nfC2tVNM9TgwQ5QuqECd6wHaFj&w=288&h=216&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2', width:200, height:150},
{path:'https://ts2.cn.mm.bing.net/th?id=OIP-C.pgBMw_lMOYSOCZIDQ896NAHaLF&w=204&h=305&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2', width:200, height:300},
{path:'https://ts1.cn.mm.bing.net/th?id=OIP-C.1-wFaNNsH_OCBFvGCfloWgHaJ4&w=216&h=288&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2', width:200, height:267},
{path:'https://ts1.cn.mm.bing.net/th?id=OIP-C.ebfLNO07a781ncjScj7u6wHaLs&w=198&h=314&c=8&rs=1&qlt=90&o=6&dpr=2&pid=3.1&rm=2', width:200, height:318},
{path:'https://tse4-mm.cn.bing.net/th/id/OIP-C.uNxOyVhFHzUweInZEahy-QHaFj?w=231&h=180&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:156},
{path:'https://tse2-mm.cn.bing.net/th/id/OIP-C.XXrW8aTA10CkicQuGceNjgHaE7?w=263&h=180&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:137},
{path:'https://tse2-mm.cn.bing.net/th/id/OIP-C.mQBvGOCQDvosB6SXXIljfwHaE0?w=304&h=197&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:130},
{path:'https://tse3-mm.cn.bing.net/th/id/OIP-C.pVMgoYOCp9YQ3BnH1AhuggHaE7?w=296&h=197&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:135},
{path:'https://tse1-mm.cn.bing.net/th/id/OIP-C.YTQX73HsbhdnueV0dzYPpAHaEW?w=314&h=184&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:120},
{path:'https://tse4-mm.cn.bing.net/th/id/OIP-C.ivx7EPOK5Y3d4J_Z2OlydgHaE7?w=260&h=180&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:140},
{path:'https://tse1-mm.cn.bing.net/th/id/OIP-C.h02sipGoXVxrZGX27e2hoQHaE3?w=234&h=180&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:154},
{path:'https://tse4-mm.cn.bing.net/th/id/OIP-C.4eNrKDHGVHrdX45iP22r6AHaEK?w=283&h=180&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:125},
{path:'https://tse1-mm.cn.bing.net/th/id/OIP-C.hNu5gDicpIJY8yCE-hM2EwHaNK?w=115&h=184&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:320},
{path:'https://tse3-mm.cn.bing.net/th/id/OIP-C.MfAU3E769jbMcGoA-WrbHwHaEK?w=294&h=180&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:123},
{path:'https://tse2-mm.cn.bing.net/th/id/OIP-C.lE3uaNuJUHCapn4EnEPFbgHaE8?w=267&h=180&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:135},
{path:'https://tse4-mm.cn.bing.net/th/id/OIP-C.OjgEXcW8FmAnIoO0bwTldwHaEK?w=297&h=183&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:124},
{path:'https://tse2-mm.cn.bing.net/th/id/OIP-C.y8bP8drHeoVXB53aMIQhIQAAAA?w=189&h=190&c=7&r=0&o=5&dpr=2&pid=1.7',width:200,height:202},
]
// 获取容器元素
let container = document.getElementById('container')
// 造图片
function createImg(imgItem) {
let img = new Image()
img.setAttribute('data-src',imgItem.path)
img.height = imgItem.height
img.className = 'item'
img.src = ''
img.alt = 'loading...'
return img
}
// 图片标签上树
function loadImg(imgArr) {
let nodeImgArr = []
for(let i = 0; i < imgArr.length; i++) {
container.appendChild(createImg(imgArr[i]))
}
// 上树完成之后 就布局
waterfall()
}
// 瀑布流布局
function waterfall() {
// 得到所有图片和定义的间距
let items = document.getElementsByClassName('item')
let padNum = 10
// 确定有多少列
let itemWidth =items[0].offsetWidth
let columns = Math.ceil(getClient().width / (itemWidth + 10)) // 向下取整
// 定义一个映射高度数组 * 核心
let heightArr = []
for (var i = 0; i < items.length; i++) {
if (i < columns) {
// 表示是第一行
items[i].style.top = 0
items[i].style.left = i * (itemWidth + padNum) + 'px'
items[i].src = items[i].dataset['src']
heightArr.push(items[i].offsetHeight)
// 更新高度
} else {
// 表示第二行开始了
// 要得到最小高度 以及最小高度所在的索引
let minHeight = Math.min(...heightArr)
let heightIdx = heightArr.findIndex((item, index) => item === minHeight)
// items[i]是否在可视区域
items[i].style.top = minHeight + padNum + 'px'
items[i].style.left = heightIdx * (itemWidth + padNum) + 'px'
// 判断是否在视口内
if(isInViewPort(items[i])) {
items[i].src = items[i].dataset['src']
}
// 更新高度
heightArr[heightIdx] += (items[i].clientHeight + padNum)
}
}
}
window.onload = function() {
// 当这个图片数组已经渲染过了之后 就不需要再加载了
loadImg(imgs1)
}
// 监听尺寸变化
window.onresize = function() {
waterfall()
if(touchBottom()) {
loadImg(imgs1)
}
}
window.onscroll = function() {
//
waterfall()
//
if(touchBottom()) {
loadImg(imgs1)
}
}
// 获取视口宽度 方法 -- 兼容
function getClient() {
return {
width: window.innerWidth || document.documentElement.clientHeight || document.body.clientHeight,
height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
}
}
// 判断元素是否在视窗内
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
top >= 0 && (top + 30) <= viewHeight
);
}
// 获取滚动值 方法 -- 兼容
function getScrollTop() {
return window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
}
// 判断页面是否触底 //页面卷去高度 + 浏览器可视窗口的高度 >= 整个页面的高度
function touchBottom() {
return getScrollTop() + getClient().height + 10 >= document.body.scrollHeight
}
</script>
</body>
</html>
参考: