我正在参加「掘金·启航计划」
前言
瀑布流布局(Waterfall layout)是一种在网页设计和用户界面设计中常用的布局方式。它的特点是将内容以类似瀑布落下的方式依次排列在页面上,从上到下依次填充,形成一列列的垂直布局,瀑布流布局的优点在于能够有效地利用空间,适应不同尺寸和比例的内容,并且能够提供更好的视觉吸引力
css实现
css实现可以通过两个css属性column-count
和column-gap
实现,但是缺点就是排列是从上往下排列为不是从左往右,然后还可以通过flexbox实现,不过也有缺点,就是要设定容器的高度,所以最后还是打算用js实现
实现瀑布流思路
通过js实现的话就要创建一个容器,用于包含瀑布流布局的元素,计算一行排列元素的个数,通过元素的个数设置每一个元素排列位置,排列方式用绝对定位来排列,计算每一个元素的left和top值
html结构
建立一个class为container的div元素作为瀑布流布局的容器并设置id为waterfall,再建立几个class为item的元素作为瀑布流布局的元素,复制多几个item元素
<div class="container" id="waterfall">
<!-- item * n -->
<div class="item">
<div class="pictrue">
<img class="thumb" src="https://img.500px.me/500px1050934340.jpg!p4" >
</div>
</div>
</div>
样式也设置一下,给每个item设一个宽度并且设置绝对定位
*{
margin: 0;
padding: 0;
background: rgb(73, 74, 95);
}
.container{
margin: 0 auto;
height: auto;
position: relative;
}
.item{
width: 200px;
height: auto;
padding: 10px;
position: absolute;
}
.item .pictrue{
padding: 10px;
border: 1px solid #a8a8a8;
border-radius: 3px;
background:#fff;
}
.item .pictrue img{
width: 100%;
}
布局和样式写好之后就是这样子的了
js实现瀑布流布局
新建一个js文件,里面创建一个init函数,函数中存放初始化瀑布流的代码,在html中引入并调用这个方法,这个方法接收一些参数,容器元素的id和瀑布流元素的类名
<!-- html -->
<script src="./index.js"></script>
<script>
waterFallInit({
id:'waterfall',
className:'item'
})
</script>
<!-- index.js -->
function waterFallInit(obj) {
// 页面加载完成后执行
window.onload = function () {
waterFall(obj);
}
}
function waterFall(obj) {
// 实现瀑布流效果
}
实现瀑布流效果就在waterFall函数中执行,先获取容器的元素,瀑布流元素和浏览器可视区域宽度,然后通过浏览器可视区域宽度 / 瀑布流元素宽度 = 一行排列元素个数,再通过一行排列元素个数 * 瀑布流元素宽度的结果作为容器的元素的宽度,这样就可以使容器的元素在html中居中了
function waterFall(obj) {
let container = document.getElementById(obj.id);
let item = document.getElementsByClassName(obj.className);
let clientWidth = document.documentElement.clientWidth;
let colCount = Math.floor(clientWidth / item[0].offsetWidth);
container.style.width = colCount * item[0].offsetWidth + 'px'
}
到这一步之后就要循环遍历每一个瀑布流元素,计算每一个瀑布流元素的left值和top值了,第一行的元素就很好计算了,top值都是0,left值则是瀑布流元素的宽度 * 当前的个数
for (let i = 0; i < item.length; i++) {
if (i < colCount) {
item[i].style.top = "0";
item[i].style.left = i * item[0].offsetWidth + "px";
}
}
可以看到第一行的瀑布流元素都被设置了left值和top值,而第一行之后的每一个元素去寻找上一行高度最小的容器并排列在它下面,定义一个变量hList来存储每一行的元素的高度
在为第一行元素设置left值和top值的时候存储当前元素的高度到hList数组里,当循环到了第一行之后的元素,获取数组的最小高度和最小高度的索引,将元素的top值设为最小高度,left值设为最小高度的索引 * 元素宽度,并让数组的最小高度加上当前元素的高度
let hList = []; //每一行盒子的高度
for (let i = 0; i < item.length; i++) {
if (i < colCount) {
item[i].style.top = "0";
item[i].style.left = i * item[0].offsetWidth + "px";
hList.push(item[i].offsetHeight);
} else {
let min = Math.min(...hList);
let index = hList.indexOf(min);
item[i].style.top = min + "px";
item[i].style.left = index * item[0].offsetWidth + "px";
hList[index] += item[i].offsetHeight;
}
}
瀑布流的效果就实现了
js实现响应式瀑布流布局
当浏览器可视宽度缩小的时候瀑布流布局也要做出相应的排列,只要浏览器可视宽度发生变化的时候重新调用waterFall函数就行了,不过得添加一个 节流函数 ,不然waterFall函数会被频繁的触发,耗费性能降低运行速度
function throttle(callback, delay) {
let flag = true;
return function () {
if (flag) {
flag = false;
setTimeout(() => {
callback.bind(this)();
flag = true;
}, delay);
}
};
}
function waterFallInit(obj) {
window.onload = function () {
waterFall(obj);
resize(obj, waterFall);
}
}
function resize(obj, callback) {
window.onresize = throttle(function () {
callback(obj);
}, 1000);
}
js实现瀑布流布局的无限加载
滚动加载通过onscroll事件实现,每次触发的时候获取最后一个瀑布流元素距离浏览器视口顶部的距离、最后一个瀑布流元素的高度和浏览器可视窗口的高度,当元素距离顶部的距离 + 元素的高度小于浏览器可视窗口的高度时触发加载,所以调用waterFallInit方法的时候传多一个回调函数
waterFallInit({
id:'waterfall',
className:'item',
cb:load
})
function load(reload){
console.log('加载更多');
}
// index.js
function waterFallInit(obj) {
window.onload = function () {
docuScroll(obj);
waterFall(obj);
resize(obj, waterFall);
}
}
function docuScroll(obj) {
document.onscroll = throttle(function () {
let item = document.getElementsByClassName(obj.className);
let lastItem = item[item.length - 1];
// 最后一个瀑布流元素距离浏览器视口顶部的距离
let itemTop = lastItem.getBoundingClientRect().top;
// 最后一个瀑布流元素的高度
let itemHeight = lastItem.offsetHeight
// 浏览器可视窗口的高度
let windowHeight = window.innerHeight;
if ( itemTop + itemHeight - 20 < windowHeight) {
if (typeof obj.cb === "function") {
obj.cb(waterFall); // 调用传递的回调函数
}
}
}, 1000);
}
模拟一下更多加载
function load(reload){
let container = document.getElementById('waterfall');
for(let i =0 ;i<5;i++){
container.innerHTML += `
<div class="item">
<div class="pictrue">
<img class="thumb" src="https://img.500px.me/500px1050979484.jpg!p4" >
</div>
</div>`
}
setTimeout(() => {
reload({
id:'waterfall',
className:'item',
})
}, 100);
}
不过有时候滚动加载并不是针对文档对象的,例如是针对容器元素来滚动,就要对容器元素的样式设置高度和overflow-y: auto
,滚动事件的对象也不是文档对象而是容器元素