瀑布流分为横向和纵向,前者是每个元素高度固定,宽度参差不齐;后者是宽度相对固定,高度不一。
虽然展现的效果不太一样,但实现原理差不多,大同小异,以下内容主要基于实际项目遇到的情况来总结的,更多的实现方式欢迎补充~~
横向的瀑布流是每个元素的高度固定,通过css的flex布局就能实现出一个大差不离的瀑布流
<template>
<div>
<div class="layout-container">
<div class="ly-item" v-for="(item, index) in list" :key="index" :style="{width: item.width + 'px'}">
<div class="ly-inner" :style="{background: item.color}">div>
div>
div>
div>
template>
<script>
const colors = ['red', 'yellow', 'blue', 'pink', 'purple', 'green', 'gray', 'skyblue'];
export default {
name: 'pubo-layout',
data() {
return {
list: [],
}
},
mounted() {
this.initList()
},
methods: {
initList() {
let list = []
for(let i = 0; i < 18; i ++) {
list.push({
width: 2 * parseInt(Math.random() * 100),
color: colors[Math.ceil(Math.random() * colors.length - 1)]
})
}
this.list = list;
}
}
}
script>
<style>
.layout-container {
margin-top: 32px;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.ly-item {
flex: none;
height: 80px;
}
.ly-inner {
height: 100%;
}
style>
emmmm…,追究起来仅靠css实现的瀑布流也是很粗糙的,勉强能用
宽度固定的瀑布流我总结出两类情况:
有一种简单粗暴的方法,通过CSS设置(CSS干啥都有些简单粗暴的赶脚~~)
即通过column-count属性来控几列,从而实现瀑布流。
column-count的具体使用方法可以看张鑫旭大佬的博客⬇️
https://www.zhangxinxu.com/wordpress/2019/01/css-css3-columns-layout/
接下来,上Demo
//
<template>
<div>
<div class="ly-vertical">
<div class="ly-item" v-for="(item, index) in list2" :key="index" :style="{height: item.height + 'px'}">
<div class="ly-inner" :style="{background: item.color}"></div>
</div>
</div>
</div>
</template>
<script>
// 找图片太麻烦了,直接用纯色块来代表吧
const colors = ['red', 'yellow', 'blue', 'pink', 'purple', 'green', 'gray', 'skyblue'];
export default {
name: 'pubo-layout',
data() {
return {
list2: [],
}
},
mounted() {
this.initList2();
},
methods: {
initList2() {
let list = []
for(let i = 0; i < 12; i ++) {
list.push({
// 模拟高度不同的元素
height: 2 * parseInt(Math.random() * 100),
color: colors[Math.ceil(Math.random() * colors.length - 1)]
})
}
this.list2 = list;
}
}
}
</script>
<style>
.ly-vertical {
width: 60%;
margin: 32px auto 0 auto;
column-count: 2; /*设置列数*/
column-gap: 2px;
}
.ly-vertical .ly-item {
break-inside: avoid; // 知识点
margin: 8px 0;
}
</style>
效果是这个样子滴,怎么说呢 , 跟flex实现的横向瀑布流一样粗糙
注意注意:
break-inside
; 表示断行方式,设置成avoid
以后,当前列剩余空间不足以存放下一个元素时,会把他整个放到第二列,而不会对该元素切分比如商品列表页,下拉到底部后会加载新数据,并且一般都要求商品的展示顺序是从左到右。这个时候单靠column-count是不行了
情景:
进入商品列表页,获取10条数据,页面下拉到底部,动态加载再次获取10条数据,依次循环。
实现思路:
接下来的所有例子都是基于 flex布局 + js动态拆分数据来实现的
// 以下代码以小程序为例
// wxml
// list-vertical设置flex布局,将内部元素分为两列
<view class="list-vertical">
// 注意这里又包了1层, 父容器display:flex时,两个good-list的高度是一样的
<view class="good-list">
// left-card,right-card这一层是为了获取高度用的
<view id="left-card">
<card-item
pt="{{pt}}"
wx:key="index"
wx:for="{{leftList}}"
info="{{item}}"
/>
</view>
</view>
<view class="good-list">
<view id="right-card">
<card-item
pt="{{pt}}"
wx:key="index"
wx:for="{{rightList}}"
info="{{item}}"
/>
</view>
</view>
</view>
// js
Component({
data: {
leftList: [],
rightList: [],
},
fetchGoodsList(beforeFetchData, options = {}) {
// 父组件传入调用的接口名
const { api } = this.properties;
let { leftList, rightList } = this.data;
if (api) {
// params 自己组装查询参数
return api(params)
.then(res => {
const {
items,
paginator: { totalCount }
} = res;
// 商品数据分为左右两列, 优先插入较短的一列
let { leftList: leftGoodsList = [], rightList: rightGoodsList = [] } = this.splitList(
leftList,
rightList,
items
);
this.setData(
{
leftList: [...leftGoodsList],
rightList: [...rightGoodsList],
}
);
})
},
splitList(left, right, newList) {
let leftList = [...left], rightList = [...right];
// 按照奇偶分
newList.forEach((item, index) => {
if(index % 2 == 0){
left.push(item)
} else {
right.push(item)
}
})
return { leftList, rightList }
}
},
})
// wcss
.list-vertical {
display: flex;
flex-direction: row;
justify-content: space-between;
}
// 每个卡片有设定好的宽度
缺点:
场景:
当搜索的商品涉及到算法的时候,因为算法的过滤去重等操作,每次拿到的数据数量就不一定了,仍然按照奇偶来划分就容易出现一侧的数据多一侧数据少。
思路:
沿用上面的思路,仍然按照奇偶拆分,但是在拆分商品数据的时候,先获取当前的左侧、右侧数组长度,优先从短数组的一侧开始插入。
const splitList = (_this, left, right, newList) => {
let leftList = [...left],
rightList = [...right];
// 判断数组长度
if (leftList.length <= rightList.length) {
pushList(leftList, rightList, newList);
} else {
pushList(rightList, leftList, newList);
}
return {
leftList,
rightList
};
};
function pushList(_left, _right, newList) {
newList.forEach((item, index) => {
if (index % 2 === 0) {
_left.push(item);
} else {
_right.push(item);
}
});
}
总结:
上面的方法只是为了应付每次查到的数据数量不同,当商品数量差太多时,还是会出现排版的问题。甚至可以说,这不是解决方法(但我就是想记录下来)
尤其当每个卡片的高度差值变得不可控的时候,这个方法肯定是不行的;
这个时候就没啥简单好用的方法了,只能开始计算左右两列的高度,来决策新的一条数据到底插入哪里。
场景:
这个时候如果只是插入简单的图片,或者说在插入可确定高度的新元素时,处理起来稍微舒服一点。
思路:
这个时候如果是在H5或者PC端就简单一些,用document.querySelector().getBoundingClientRect()
获得容器高度,累加计算就完事了(或者不用获取dom高度,本地维护一个变量记录左、右两列的高度也可以。)
splitList() {
// 或者本地维护一个left, right 值来记录左右两侧的高度
//反正每个元素的padding,margin等是一样的,不影响计算的准确性
let left = document.querySelector('#left-list').getBoundingClientRect(),
right = document.querySelector('#right-list').getBoundingClientRect();
goodsList.forEach(item => {
let h = item.height;
if (left <= right) {
leftList.push(item);
left += h;
} else {
rightList.push(item);
right += h;
}
})
}
更省心舒服的方法,可以使用第三方插件 Masonry,配置化的实现瀑布流布局,简单好用。
Masonry算是很好用的瀑布流的插件了。
但是小程序里是不支持document.querySelector的,所以Masonry在小程序里就无能为力了
小程序里要获得DOM元素的信息时,需要用小程序自带的wx.createSelectorQuery(),详细用法可以找官方文档⬇️
https://developers.weixin.qq.com/miniprogram/dev/api/wxml/wx.createSelectorQuery.html
直接上示例代码:
// 封装一个Behavior,在组件里通过设置behaviors: [basic]属性来使用
export const basic = Behavior({
methods: {
getRect(selector, all) {
return new Promise(resolve => {
wx.createSelectorQuery()
.in(this)[all ? 'selectAll' : 'select'](selector)
.boundingClientRect(rect => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect);
}
if (!all && rect) {
resolve(rect);
}
})
.exec();
});
}
}
});
// 获取元素高度
this.getRect(select).then(res => {
// 高度
console.log(res.height);
})
如何获取元素高度?
有bindload可以获得图片高度,插入图片后获得到实际高度,累加到左侧或者右侧的列表高度商品列表的商品以卡片形式展示,可能有营销标签,商品名很长用两行展示;
因为1,所以每个卡片在渲染到页面之前是不知道具体高度的,只知道每个卡片的高度差不会太大;
商品通过算法过滤,每次拿到的商品数量不一定;
在小程序里的,尽量不要性能差的太明显
最后我想到的偷懒方法:
优点:
缺点:
以上主要是在实现商品列表是对瀑布流的实现方法的汇总,通过flex布局+js拆分数据来实现的简单瀑布流;
鉴于商品卡片里包含了图片,商品名,营销标签等内容,导致商品卡片高度不确定,只有渲染到页面上以后才能明确高度;而且每次拿到的商品数量也不确定,所以通过索引奇偶拆分的方法就行不通了,必须要计算一下左右商品列表的高度才行。
出于性能考虑,最后决定用末尾的一个商品来填充短的商品列表,尽可能的做到瀑布流的效果。
当然瀑布流里其实很有很多学问,比如还可以通过absolute定位来排列,或者高级一点的可以做到高度、宽度同时瀑布流,这就涉及到动态规划之类的算法了。
有更好的方法,欢迎补充~~