简易瀑布流TS版

一、前言

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是Pinterest,逐渐在国内流行开来。国内大多数清新站基本为这类风格。

适用于以图片为主的网站,配合懒加载效果还是不错的

二、代码

HTML

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>瀑布流</title>
<style>
.container {
position: relative;
margin: 0 auto;
padding: 0;

width: 1290px;
}

.box {
position: absolute;
background-color: #eee;
color: #5a5756;
display: flex;
justify-content: center;
align-items: center;
}
.loading{
position: fixed;
width: 100%;
height: 100%;
top:0;
bottom: 0;
left: 0;
right: 0;
background-color: #eee;
display: none;
justify-content: center;
align-items: center;
}
</style>
</head>
<body>
<div class="container">
</div>
</body>
<script src="./index.js"></script>
</html>

TS源码

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
let column: number; // 多少列
let heightArr: Array<ColumnNode> = []; // 每一列高度和当前列渲染了几个子节点
let containerWidth: number; // 容器宽度
let initPicNum: number; // 初始化加载照片数量
let gapWidth: number; // 间距宽度(像素)
let projectWidth: number; // 每个项目的宽度
let isLoading: boolean = false; // 是否正在加载数据

// 保存每一列高度和
interface ColumnNode {
height: number;
times: number;
index: number;
}

interface JsonConfig {
column: number;
containerWidth: number;
initPicNum: number;
gapWidth: number;
}

window.onload = function() {
// 初始化保存每一列行高的数据
readJsonConfig().then(configText => {
let jsonObj: JsonConfig = JSON.parse(configText);
initData(jsonObj);
// 根据最外层容器和列数初始化容器和子元素
justifyBox(containerWidth, column, gapWidth);
// 初始化加载图片
randomPic(initPicNum, column);
});
window.onscroll = function() {
let scroll = document.documentElement.scrollTop | document.body.scrollTop;
const screenHeight = window.innerHeight;
if (document.body.clientHeight < scroll + screenHeight) {
// 到达底部
randomPic(1 * column, column);
}
};
};

// 初始化数据和相关配置
const initData = (config: JsonConfig): void => {
for (var i = 0; i < config.column; i++) {
heightArr.push({
height: 0,
times: 0,
index: i
});
}
column = config.column;
containerWidth = config.containerWidth;
initPicNum = config.initPicNum;
gapWidth = config.gapWidth;
};

const readJsonConfig = (): any => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open('get', './config.json');
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
resolve(xhr.responseText);
} else {
alert('读取配置失败');
}
}
};
});
};

// 绘制一行
const paintInitPic = (arr: Array<number>, projectWidth: number, gapWidth: number): void => {
// 找到heightArr中最低的一列,并获取到初始的索引,所以应该是对heightArr进行排序
// 排序,在最矮的一列追加一个元素
heightArr.sort((item1: any, item2: any) => {
return item1.height - item2.height;
});
// 从最矮的元素开始处理
let container: any = document.querySelector('.container');
// 逐个处理
arr.forEach((item, index) => {
// 排序后,index就是最矮的那一列
let box: any = document.createElement('div');
box.className = 'box';
// 位置和高度
box.style.height = item + 'px';
box.innerHTML = `${projectWidth} x ${item}`;
box.style.width = projectWidth + 'px';
box.style.left = (projectWidth + gapWidth) * heightArr[index].index + 'px';
box.style.top = heightArr[index].height + gapWidth * heightArr[index].times + 'px';
heightArr[index].height += item;
heightArr[index].times++;
container.appendChild(box);
});
container.style.height = Math.max(...heightArr.map(item => item.height)) + 'px';
};

// 模拟图片
const randomPic = (picNum: number, columns: number): void => {
let arr: Array<Array<number>> = [];
let randomArr: Array<number> = [];
for (let i: number = 0; i < picNum; i++) {
randomArr.push(randomNum(200, 400));
}
while (randomArr.length > 0) {
arr.push(randomArr.splice(0, columns));
}
arr.forEach(arr => {
paintInitPic(arr, projectWidth, gapWidth);
});
};

// 随机数函数
const randomNum = (min: number, max: number): number => {
return Math.round(Math.random() * (max - min) + min);
};

// 对容器宽进行设置
const justifyBox = (containerWidth: number, columns: number, gapWidth: number): void => {
let container: any = document.querySelector('.container');
container.style.width = containerWidth + 'px';
const gap: number = columns - 1;
// 计算每个"项目"的宽度
const itemWidth = (containerWidth - gap * gapWidth) / columns;
// 全局赋值,方便在下面的方法中使用
projectWidth = itemWidth;
Array.prototype.forEach.call(container.children, function(item) {
item.style.width = itemWidth + 'px';
});
};