一、使用javascript原生实现
通过 js 计算每个元素的位置,然后使用 CSS 的绝对定位将它们放置在正确的位置。
1.1 实现思路
- 获取所有的子元素;
- 根据屏幕宽度和单个子元素的宽度,获得应该展示的列数,根据列数创建对应长度的数组;
- 遍历子元素,根据以下场景作区分:
- 如果 i 小于列数,说明当前子元素应该在第一行,记录当前子元素的 offsetHeight 放在对应的数组位置上;
- 否则,获取数组中最小的值的 index 并记录下来,设置当前子元素的 position 为 absolute,top 为 index 对应的值,left 为 index 对应的 offsetLeft,并将 index 对应的值改成 “当前子元素的 offsetHeight ➕ 之前的值“。
盒子之间的间距建议使用padding,因为需要使用offsetHeight计算盒子的高度,offsetHeight只包括padding,不包括margin。
具体实现可查看下面代码👇
js代码实现:
window.onload = function () {
waterfall('main', 'box');
let dataInt = {
data: [
{src: '1.jpeg'},
{src: '2.jpeg'},
{src: '3.jpeg'}
]
}
window.onscroll = () => {
if (checkScrollSlide()) {
// 将数据块渲染到当前页面的尾部
for (let i = 0; i < dataInt.data.length; i++) {
let oParent = document.getElementById('main');
let oBox = document.createElement('div');
oBox.className = 'box';
oParent.appendChild(oBox);
let oPic = document.createElement('div');
oPic.className = 'pic';
oBox.appendChild(oPic);
let oImg = document.createElement('img');
oImg.src = `images/${dataInt.data[i].src}`;
oPic.appendChild(oImg);
}
waterfall('main', 'box');
}
};
};
const waterfall = (parent, box) => {
// 将main下的所有class为box的元素取出来
let oParent = document.getElementById(parent);
let oBoxs = getByClass(oParent, box);
// 计算整个页面显示的列数(页面宽/box的宽)
let oBoxW = oBoxs[0].offsetWidth;
let cols = Math.floor(document.documentElement.clientWidth / oBoxW);
// 设置main的宽
oParent.style.cssText = 'width:' + oBoxW * cols + 'px; margin: 0 auto;';
let hArr = []; // 存放每一列高度的数组
for (let i = 0; i < oBoxs.length; i++) {
if (i < cols) {
hArr.push(oBoxs[i].offsetHeight);
} else {
let minH = Math.min.apply(null, hArr);
let index = getMinHIndex(hArr, minH);
oBoxs[i].style.position = 'absolute';
oBoxs[i].style.top = minH + 'px';
oBoxs[i].style.left = oBoxs[index].offsetLeft + 'px';
hArr[index] += oBoxs[i].offsetHeight;
}
}
};
// 根据class获取元素
const getByClass = (parent, className) => {
let boxArr = []; // 用来存储获取到的所有class为box的元素
let oElements = parent.getElementsByTagName('*');
for (let i = 0; i < oElements.length; i++) {
if (oElements[i].className === className) {
boxArr.push(oElements[i]);
}
}
return boxArr;
};
const getMinHIndex = (arr, val) => {
let min = arr.findIndex(i => i === val);
return min;
};
// 检测是否具备了滚动条加载数据块的条件
const checkScrollSlide = () => {
let oParent = document.getElementById('main');
let oBoxs = getByClass(oParent, 'box');
let lastBoxH = oBoxs[oBoxs.length - 1].offsetTop + Math.floor(oBoxs[oBoxs.length - 1].offsetHeight / 2);
let scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
let height = document.body.clientHeight || document.documentElement.clientHeight;
return lastBoxH < scrollTop + height;
}
html代码实现:
<div id="main">
<div class="box">
<div class="pic">
<img src="images/1.jpeg" />
</div>
</div>
...
</div>
css代码实现:
body {
margin: 0;
}
#main {
position: relative;
}
.box {
padding: 15px 0 0 15px;
float: left;
}
.pic {
padding: 10px;
border: solid 1px #ccc;
border-radius: 5px;
box-shadow: 0 0 5px #ccc;
}
.pic img {
width: 250px;
height: auto;
}
1.2 优缺点
- 优点:图片排序是按照图片计算的位置横向排列,位置是计算出来的,比较规范
- 缺点:需要计算,列数 = 浏览器窗口宽度 / 子元素宽度,图片定位是根据每一列数据块的高度计算接下来图片的位置
二、css3多栏布局实现
2.1 使用column属性
通过 CSS 的列布局(column layout),将内容分为多列,使其呈现出瀑布流的效果。使用 column-count 和 column-gap 来设置列数和列之间的间隔。
css代码实现:
#main {
/* 每一列宽度 */
column-width: 202px;
/* 最大列数 */
column-count: 5;
/* 列间隔 */
column-gap: 5px;
}
2.1.1 优缺点
-
优点:不需要计算,浏览器自动计算,只需设置列宽,性能高
-
缺点:不可横向排列,只能纵向排列,打乱图片的排列顺序
2.2 使用flex布局
css代码实现:
#main {
display: flex;
flex-wrap: wrap;
flex-direction: column;
/* 容器必须有固定高度,且高度大于最高的列高 */
height: 2000px;
width: 500px;
align-content: space-between;
}
2.2.1 优缺点
- 优点:可实现横向排列的瀑布流
- 缺点:容器必须有固定高度,并且高度要大于最高的列高
三、现成的库
Masonry.js、Isotope等一些开源的 JavaScript 库,专门用于实现瀑布流布局。使用这些库可以简化开发流程,提供更多的配置选项和动画效果。