方案一 absolute + js

常用方案,但性能消耗大

1
2
3
4
5
6
import Waterfall from "./js/Waterfall";
window.onload = new Waterfall({
$el: document.querySelector(".img-wrapper"),
count: 4,
gap: 10,
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
export default class Waterfall {
constructor(options) {
this.$el = null; // 父容器
this.count = 4; // 列数
this.gap = 10; // 间距
Object.assign(this, options);
this.width = 0; // 列的宽度
this.items = []; // 子元素集合
this.H = []; // 存储每列的高度方便计算
this.flag = null; // 虚拟节点集合
this.init();
}
init() {
this.items = Array.from(this.$el.children);
this.reset();
this.render();
}
reset() {
this.flag = document.createDocumentFragment();
this.width = this.$el.clientWidth / this.count;
this.H = new Array(this.count).fill(0);
this.$el.innerHTML = "";
}

render() {
const { width, items, flag, H, gap } = this;
items.forEach((item) => {
item.style.width = width + "px";
item.style.position = "absolute";
let img = item.querySelector("img");
if (img.complete) {
let tag = H.indexOf(Math.min(...H));
item.style.left = tag * (width + gap) + "px";
item.style.top = H[tag] + "px";
H[tag] += (img.height * width) / img.width + gap;
flag.appendChild(item);
} else {
img.addEventListener("load", () => {
let tag = H.indexOf(Math.min(...H));
item.style.left = tag * (width + gap) + "px";
item.style.top = H[tag] + "px";
H[tag] += (img.height * width) / img.width + gap;
flag.appendChild(item);
this.$el.append(flag);
});
}
});
this.$el.append(flag);
}
}

初始化,计算出列宽来,将 H 作为列高存储器,4 列那么就是[0,0,0,0]。然后收集子元素后,清除父容器内容。
遍历其子元素,设置其都为绝对定位,设置其列宽。后监听其下的图片加载是否完毕。
如果加载成功,那么计算应该在的位置,瀑布流的常规原则是哪一列数值最小就在那一列上设置新图片。当然他的相对高度和间距也要计算出来,同时在 H 当前列上要把高度存起来。
每次图片加载完就更新虚拟节点到父容器中。

方案二 grid + js

子元素高度差过大的情况下不建议使用

1
2
3
4
5
6
7
display: grid;
grid-auto-rows: 5px;
// 子元素自动宽度,两列,5px间距
grid-template-columns: repeat(auto-fill, calc(50% - 5px));
// 防止布局错乱
align-items: start;
justify-content: space-between;
1
2
3
4
5
6
// 获取子元素
const el = this.$refs.root.$el;
// 获取子元素高度所占的格子数 + 2个格子的间距
const rows = Math.ceil(el.clientHeight / 5) + 2;
// 设置子元素高度
el.style.gridRowEnd = `span ${rows}`;

方案三 column

先排上下再排左右,多次加载数据的场景不建议使用

1
2
column-count: 4; //列数
column-gap: 10px; //列间距

方案四 grid

兼容性感人,不建议使用

1
grid-template-rows: masonry;