写在前面
最近看到了很多实现前端瀑布流布局的文章,大致自己想了想思路,今晚有空实现了一下。下面把我的思路放在这里供大家参考。
代码仓库地址:gitee.com/piao-chen/s…
实现思路
有人实现瀑布流想用纯CSS,我感觉这个很有难度,因此我用了JS。具体思路说白了其实很简单,瀑布流大致上是一个按列排布的内容,一张新的图片会被放在当前累计高度最小的列上,而每一列的位置(距离左侧)是固定的。
明白这一点后,我们就有了如下思路:
- 准备一些图片,我将其存在同目录下的media文件夹中,命名为1.jpg, 2.jpg, 3.jpg, 4.jpg,在一开始,随机选取这四个的图片,随机选取50-60次放到一个数组里,作为瀑布流的图片素材。
- 要实现响应式的(当页面宽度够宽的时候,展示为三列,不然就是展示为两列),因此需要监视window的resize事件,同时,应该为其添加防抖。
- 处理图片位置应该使用了绝对定位进行布局,我们调整他们的left和top属性值,其中left属性应该是固定的三个或者两个值(看到底有几列来决定),top则需要进行如下计算:用一个数组中的三个或者两个元素存储对应的三列或者两列的累计高度(本列所有图片间距+图片大小的和),每次都选取当前累计高度最小的列放置新图片,将这个高度作为新图片的top的属性值,同时更新本列的累计高度(累加上本图片高度与间距),重复进行直到所有图片布局完成。
- 注意:具体实现过程中,我遇到了图片加载未完成无法读取其高度(图片高度始终为0)的问题,最终用了一个遮罩层来settimeout稍微等一会来解决了这个问题。
具体代码
先看实现效果:piao-chen.gitee.io/simple-wate… (在线展示地址 需要电脑端打开)。大家可以自己调整一下自己的浏览器窗口大小来看一看响应式改变布局的效果。
项目结构:
具体代码:
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<div class="loading-page" id="loading">
<!-- 遮罩层 -->
<span>loading......</span>
</div>
<div id="app" class="not-see">
</div>
</body>
<script src="./index.js"></script>
</html>
css:
*{
margin: 0;
padding: 0;
}
html, body, #app{
position: relative;
width: 100%;
height: 100%;
}
.not-see{
display: none;
}
.waterfall-img{
position: absolute;
width: 200px;
transition: all .2s;
}
.loading-page{
position: absolute;
font-weight: 600;
font-size: 32px;
color: #fff;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: rgba(0,0,0,.3);
z-index: 99;
}
.loading-page span{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
js:
// 首先获取一下容器
var container = document.getElementById("app");
var loading = document.getElementById("loading");
function getRandomNum(Min,Max){// 整个随机数函数
var Range = Max - Min;
var Rand = Math.random();
return(Min + Math.round(Rand * Range));
}
let total; //要多少张图片
// 存储着图片的数组
var imgs = [];
// 向其中随机填入图片以及路径
function initImg(){ // 随机选取图片,选取随机次组成瀑布流图片素材
total = getRandomNum(50, 60);
for(let i = 0; i<total; ++i){
let img = window.document.createElement("img");
img.src = `./media/${getRandomNum(1, 4)}.jpg`;
img.classList.add("waterfall-img");
imgs.push(img);
container.appendChild(img);
}
}
let recordHeight = []; // 记录每一列的累计高度的数组
function getMinHeight(){ // 得到当前累计高度最小的那列在累计数组中的下标
let min = 999999999;
let index = 0;
let i;
for(i = recordHeight.length-1; i>=0; --i){
if(recordHeight[i] <= min){
min = recordHeight[i];
index = i;
}
}
return index;
}
function setPos(){
// 先判定当前应该如何进行布局,到底是两列还是三列
var windowWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; // 窗口宽度
if(windowWidth <= 900){
recordHeight = [0, 0];// 两列
let pairLeft = ["10vw", "60vw"];
// 使用两列
imgs.forEach(item=>{
// 处理每一张,找到在哪一列合适(累计高度最小列),并更新选中列的数据
let temp = getMinHeight();
item.style.top = recordHeight[temp] + 'px';
item.style.left = pairLeft[temp];
recordHeight[temp] += item.height;
recordHeight[temp] += 13;
});
}else{
// 使用三列布局
recordHeight = [0, 0, 0];
let pairLeft = ["30vw", "45vw", "60vw"];
imgs.forEach(item=>{
// 处理每一张,找到在哪一列合适(累计高度最小列),并更新选中列的数据
let temp = getMinHeight();
item.style.top = recordHeight[temp] + 'px';
item.style.left = pairLeft[temp];
recordHeight[temp] += item.height;
recordHeight[temp] += 10;
});
}
}
// 监听窗口大小改变事件,同时添加防抖
var timer = null;
window.addEventListener("resize", ()=>{
if(timer){
clearTimeout(timer);
}
timer = setTimeout(()=>{
setPos();
timer = null;
}, 300);
});
// yuanshen,启动!
initImg();
setTimeout(()=>{
loading.classList.add("not-see");
container.classList.remove("not-see");
setPos();
}, 300);