瀑布流的参考实现可以看这篇博客:3种方式实现瀑布流布局
1、利用css实现瀑布流
/**
flex布局实现
*/
.box {
display: flex;
align-items: center;
flex-flow:column wrap;
height: 100vh; // 为啥要设置100vh才有效果
}
.item {
border: 1px solid #5592e5;
margin: 10px;
width: calc(100%/3 - 20px);
}
/**
column布局实现
*/
.box {
margin: 40px;
column-count: 3;
column-gap: 40px;
}
.item {
box-sizing: border-box;
border: 1px solid #5592e5;
margin-bottom: 40px;
transition: all .5s;
&:hover{
transform: scale(1.1,1.1);
}
}
css实现有个缺点,它的实现原理是按列来排,先排满第一列,排不满的进入第二页,这个时候最后的呈现效果就可能存在最后一行时,中间有个块,显示不好。
另外还可能出现下面这种情况,该列的最后一个块的部分到了下一列首部。
2、利用js实现
可以自定义列数、行间距、列间距,注意margin是用户在对父组件设置了margin的情况下需要填写的,没有设置可以不填。
/**
* @description:
* @param {*} vm : this
* @param {*} parent : 瀑布流组件
* @param {*} arg:{cols : 设置列数, margin:父元素设置的margin, vgap:块之间的水平间隙,hgap : 块之间的竖直间隙}
* @return {*}
*/
function waterFall(vm, parent, arg) {
const divVGap = arg.vgap || 20
const divHGap = arg.hgap || 20
const columns = arg.cols || 3; //默认3列
const margin = arg.margin || 0;
// 1 确定图片的宽度 - 滚动条宽度
const pageWidth = parent.clientWidth - divVGap * columns - margin * 2 - 8;
const itemWidth = parseInt(pageWidth / columns); //得到item的宽度
const list = Array.from(parent.children)
// 设置到item的宽度
list.forEach((item, i) => {
item.style.width = itemWidth + 'px'; //设置到item的宽度
})
vm.$nextTick(() => {
setTimeout(() => {
const arr = []; // 存储每列当前高度
list.forEach((item, i) => {
const height = item.clientHeight || item.offsetHeight;
if (i < columns) {
// 2 第一行按序布局
item.setAttribute("style", `top:0; left:${(itemWidth) * i + divVGap * i}px; width:${itemWidth}px`)
//将行高push到数组
arr.push(height);
} else {
// 其他行
// 3 找到数组中最小高度 和 它的索引
// 这种写法虽短,但没有下面的效率高
// const minHeight = Math.min(...arr);
// const index = arr.findIndex(item => item === minHeight);
let minHeight = arr[0];
let index = 0;
for (let j = 0; j < arr.length; j++) {
if (minHeight > arr[j]) {
minHeight = arr[j];
index = j;
}
}
// 4 设置下一行的第一个盒子位置 top值就是最小列的高度
// 1.8ms:耗时
// item.style.top = `${arr[index] + divHGap}px`
// item.style.left = `${(itemWidth) * index + divVGap * index}px`
// 0.3ms: 多个设置和合并成一个设置,性能更优,注意要加上width设置,此操作会把上面的width覆盖掉
item.setAttribute("style", `top:${arr[index] + divHGap}px; left:${(itemWidth) * index + divVGap * index}px; width:${itemWidth}px`)
// 5 修改最小列的高度
// 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 竖直间隔
arr[index] = arr[index] + height + divHGap;
}
})
},100)
})
}
//clientWidth 处理兼容性
function getClient() {
return {
width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
}
}
export { getClient, waterFall }
这里的vm.$nextTick 和 setTimeout
都是为了正确的获取高度,但是这个处理不准确,图片的加载不知道什么时候结束,所以这里可以使用图片的懒加载插件,在图片加载之后进行setTimeOut中间的操作。如vue-lazyload 等
在vue文件使用
这里监控onsize的变化可以跟随页面变化而调整瀑布流。
<template>
<div class="box" ref="box" id="box">
<div class="item" ref="item">
<img src="@/assets/image/not-exist@2x.png" alt="" />
</div>
<div class="item">
<img src="@/assets/image/1@2x.png" alt="" />
</div>
</div>
</template>
<script>
import { waterFall } from '@/assets/js/waterfall'
export default {
mounted() {
waterFall(this, this.$refs.box, { margin:40, vgap:40, hgap:30})
// 页面尺寸改变时实时触发
window.onresize = () => {
//重新定义瀑布流
waterFall(this, this.$refs.box,{ margin:40, vgap:40, hgap:30})
}
},
}
</script>
<style lang="scss" scoped>
img{
width: 100%;
height: 100%;
}
.box {
margin: 40px;
width: 100%;
position:relative;
box-sizing: content-box;
}
.item {
position: absolute;
border: 1px solid #5592e5;
transition: all .3s;
cursor: pointer;
&:hover{
transform: scale(1.05,1.05);
}
}
</style>
总结
这里只是简单的实现,后续还可以加上下拉加载等功能。
这里有些推荐瀑布流插件:https://www.cnblogs.com/ruruo/p/12938878.html,
vue-waterfall:这个要传入入瀑布流每项的宽高,这块可以通过上述代码的方式动态设置每个块的宽高。还可以自定义设置一些块位置变换时(页面缩小时)的过渡效果
vue-grid-layout : 提供自由拖拽的功能,官网上还有几个例子