前言
随着前端的发展,相信瀑布图大家应该都不陌生,也许你不一定了解但是你一定见过。
用一张蘑菇街的官网照片:
这些都是经典的瀑布图布局,只不过这些他们使用的是多流瀑布图,而这篇博客我带着大家一起来看看稍微简单一点的双流瀑布图的实现,不管是多流还是双流,其底层原理都是一致的。
思路
我们先来看一个已经实现好的一个双流瀑布图的样例:
首先,一种简单且比较直接的想法是直接使用css布局:flex布局。
flex布局
观察双流瀑布图,我们不难发现其实它每列的宽度是固定的,那么我们完全可以使用如下规则:父元素使用flex,每个子元素固定宽度,然后父元素使用撑开自动换行并且分配均匀剩余空间的方式。
代码如下:
<!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>
#box {
display: flex;
width: 400px;
background-color: #fff;
justify-content: space-between;
flex-wrap: wrap;
}
.fall {
width: 190px;
}
.fall-1 {
height: 100px;
background-color: aquamarine;
}
.fall-2 {
height: 150px;
background-color: blueviolet;
}
.fall-3 {
height: 120px;
background-color: coral;
}
.fall-4 {
height: 80px;
background-color: darkgreen;
}
</style>
</head>
<body>
<div id="box">
<div class="fall-1 fall">1</div>
<div class="fall-2 fall">2</div>
<div class="fall-3 fall">3</div>
<div class="fall-4 fall">4</div>
</div>
</body>
</html>
效果图如下:
虽然也能实现双流瀑布的效果,但是我们发现它少了一个很明显的“无缝衔接”的效果,这个效果本就是flex布局的机制所导致的。
那么使用flex布局就当真无法实现此无缝衔接的双流瀑布图效果吗?答案是否定的,当然可以实现。
flex布局进阶版
既然直接使用flex换行撑开无法做到无缝衔接,那么我们不妨换个角度:因为列的宽度是固定的,我们可以先把每列确定,然后让元素去无缝衔接的填充每列即可。
但是此时又会引发一个问题,我们又如何让元素去无缝衔接的填充,即哪个元素应该填充到哪一列呢?
此时,我们就必须使用js进行计算,计算时只需满足一个条件即可:哪列最短填充哪列,同等高度优先从左开始。
核心如下:
if (height2 < height1) {
col2.append(targetDom);
} else {
col1.append(targetDom);
}
下面给出完整案例的代码:
<!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>
#box {
display: flex;
width: 400px;
background-color: #fff;
justify-content: space-between;
flex-wrap: wrap;
}
.col-1,
.col-2 {
width: 190px;
display: flex;
flex-direction: column;
}
.fall {
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="box">
<div class="col-1">
</div>
<div class="col-2">
</div>
</div>
<button onclick="clickOne()">单个添加</button>
<button onclick="clickAll()">批量添加</button>
<script>
let index = 0;
let heightLeft = 0;
let heightRight = 0;
function randomColor() {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return 'rgba(' + r + ',' + g + ',' + b + ')';
}
function randomHeight(min, max) {// 20-100
return Math.floor(Math.random() * (max - min)) + min;
}
function getHeight(dom) {
let height = getComputedStyle(dom).height;
height = Number(height.substring(0, height.length - 2));
return height
}
function getSum(dom) {
const children = dom.children;
let sum = 0;
for (let i = 0; i < children.length; i++) {
sum += getHeight(children[i]);
}
return sum;
}
function clickOne() {
cacheAdd(randomHeight(20, 100));
}
function clickAll() {
const arr = [];
for (let i = 0; i < 20; i++) {
arr.push(randomHeight(20, 100));
}
addAll(arr);
}
// 对于新增高度已知的情况,可以使用缓存的方式来避免频繁操作dom
function cacheAdd(targetHeight) {
const col1 = document.getElementsByClassName('col-1')[0];
const col2 = document.getElementsByClassName('col-2')[0];
const targetDom = document.createElement('div');
targetDom.style.height = targetHeight + 'px';
targetDom.style.backgroundColor = randomColor();
targetDom.textContent = index++;
targetDom.classList.add('fall');
if (heightRight < heightLeft) {
col2.append(targetDom);
heightRight += targetHeight;
} else {
col1.append(targetDom);
heightLeft += targetHeight;
}
}
function add(targetHeight) {
const col1 = document.getElementsByClassName('col-1')[0];
const col2 = document.getElementsByClassName('col-2')[0];
const height1 = getSum(col1);
const height2 = getSum(col2);
const targetDom = document.createElement('div');
targetDom.style.height = targetHeight + 'px';// 自适应填充
targetDom.style.backgroundColor = randomColor();
targetDom.textContent = index++;
targetDom.classList.add('fall');
if (height2 < height1) {
col2.append(targetDom);
} else {
col1.append(targetDom);
}
}
function addAll(arr) {
for (let i = 0; i < arr.length; i++) {
cacheAdd(arr[i]);
}
}
</script>
</body>
</html>
效果如下:
下面有两个需要补充的地方:
- 代码的核心函数部分分成两种情况:一种是填充元素高度已知,另一种是填充元素高度未知。对于填充元素高度已知的情形,可以使用全局变量进行高度和缓存从而实现性能优化的目的。
- 对于一次性填充多个元素的情形,虽然视觉上是一次性填充,但是在代码的层面我们只能循环调用单次填充函数从而达到一次性填充多个元素的目的,因为下一个元素的填充位置取决于上一个元素的填充情况。
到此,使用flex布局就实现了瀑布图的效果,那么还有没有其它方式也能实现双流瀑布图呢?
答案当然是有的。
绝对定位/font>
使用绝对定位的思路和flex布局的思路有很多相似的地方:都是先确定列的总数然后填充每列的元素。
但是不一样的是,flex是自动填充而绝对定位是得手动改变需要填充元素的top值以及父元素的高度进行填充。
Talk is cheap,show you my code:
<!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>
#box {
display: flex;
width: 400px;
background-color: #fff;
justify-content: space-between;
flex-wrap: wrap;
}
.col-1,
.col-2 {
width: 190px;
position: relative;
}
.fall {
position: absolute;
left: 0;
right: 0;
}
</style>
</head>
<body>
<div id="box">
<div class="col-1">
</div>
<div class="col-2">
</div>
</div>
<button onclick="clickOne()">单个添加</button>
<button onclick="clickAll()">批量添加</button>
<script>
let index = 0;
let heightLeft = 0;
let heightRight = 0;
function randomColor() {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return 'rgba(' + r + ',' + g + ',' + b + ')';
}
function randomHeight(min, max) {// 20-100
return Math.floor(Math.random() * (max - min)) + min;
}
function cacheAdd(targetHeight) {
const col1 = document.getElementsByClassName('col-1')[0];
const col2 = document.getElementsByClassName('col-2')[0];
const targetDom = document.createElement('div');
targetDom.style.height = targetHeight + 'px';
targetDom.style.backgroundColor = randomColor();
targetDom.textContent = index++;
targetDom.classList.add('fall');
if (heightRight < heightLeft) {
targetDom.style.top = heightRight + 'px';
col2.append(targetDom);
heightRight += (targetHeight + 10);
col2.style.height = heightRight + 'px';
} else {
targetDom.style.top = heightLeft + 'px';
col1.append(targetDom);
heightLeft += (targetHeight + 10);
col1.style.height = heightLeft + 'px';
}
}
function addAll(arr) {
for (let i = 0; i < arr.length; i++) {
cacheAdd(arr[i]);
}
}
function clickOne() {
cacheAdd(randomHeight(20, 100));
}
function clickAll() {
const arr = [];
for (let i = 0; i < 20; i++) {
arr.push(randomHeight(20, 100));
}
addAll(arr);
}
</script>
</body>
</html>
不难发现,使用绝对定位的方式显得比使用flex会更加麻烦:
- 绝对定位的方式每次添加子元素时都需要手动改变父元素的高度。
- 绝对定位的方式对于目标元素高度未知的情形不友好,因为此时我们无法立即更新父元素的高度。(但是并不是不能,留给大家自行思考。)
结语
到此,本篇文章的讲述已临近尾声,这篇博客介绍了两种双流瀑布图的实现方式,这里在实际应用中我更建议大家使用第一种方式。虽然这两种方式进而也可以实现多流瀑布图,但是他俩都无法直接实现那么自适应的多流瀑布图,即流的数量随着窗口大小的改变而改变,不过其基本思路也是此博客所讲的双流的解题思路,大家有兴趣可以自行深入研究。最后,对于本篇文章大家有任何疑问或者建议,欢迎评论区留言或者私信我,我会积极给与回复,下期再会。