封装小程序瀑布流布局组件
最终效果展示如下:
需求分析
1、瀑布流列数随着图片的宽度而计算一屏幕多少列
2、以为两列为例,两列的高度随着图片的插入而改变
3、默认给左右两列插入一张图片,插入图片后,根据两列的的高度做为判断依据,依次给高度更低的一列插入图片
4、使用slot插槽来自定义图片的信息
在component文件夹下新建文件
在需要瀑布流页面的json文件引入组件
第一步、先设置组件wxml布局
<view class="waterfall-container" style="height:{{ height }}px">
<view
class="water-item"
wx:for="{{ waterfallarr }}"
id="waterfall-item-id-{{ index }}"
wx:key="this"
style="width:{{ itemwidth }}px;height:auto;top:{{ allPositionArr[index].top }}px;left:{{ allPositionArr[index].left }}px">
<image
class="lazyimg {{ item.show ? 'loadimg' : '' }}"
src="{{ item.image_url }}"
mode="widthFix"
data-index="{{ index }}"
bindload="loadImgFinish"></image>
<view class="water-content">
<slot name="slot{{ index }}"></slot>
</view>
</view>
</view>
image组件的mode设置为widthFix为了让图片适应容器的宽度并且等比例缩放,绑定bindload事件触发loadImgFinish方法获取图片加载完后的信息
wxss样式如下
page{
background: #f5f5f5;
}
.waterfall-container{
position: relative;
width: 100%;
height: auto;
box-sizing: border-box;
overflow: hidden;
}
.water-item{
display: flex;
flex-direction: column;
justify-content: center;
position: absolute;
top: 0;
left: -50%; /* 防止一开始图片位置堆积在一个地方 */
border-radius: 10rpx;
overflow: hidden;
background: #cccccc;
}
.water-item image{
width: 100%;
}
.title{
padding: 16rpx 16rpx 0 16rpx;
font-size: 28rpx;
color: #333333;
font-weight: 600;
}
.water-content{
background: #ffffff;
}
.basic-info{
padding: 16rpx;
font-size: 28rpx;
display: flex;
justify-content: space-between;
align-items: center;
}
.price{
color: red;
font-size: 26rpx;
}
.evaluation{
font-size: 24rpx;
color: #999999;
}
.lazyimg{
border-top-left-radius: 14rpx;
border-top-right-radius: 14rpx;
transition: opacity .5s ease;
opacity: 0;
}
.loadimg{
opacity: 1;
}
组件具体的参数说明
参数说明:
col:为设置的瀑布流列数
blockspace:为图片之间的间距
waterfall:为从父组件获取到的瀑布流数据,当dataFall的数据出现改变时候,在observer:function(){}的异步操作中把数据赋值给waterfallarr
properties: {
col: {
type: Number,
value: 2,
observer: function(newVal,oldVal){}
},
blockspace:{
type: Number,
value: 5,
observer: function(newVal,oldVal){}
},
waterfall: {
type: Array,
value: [],
observer: function(newVal,oldVal){
this.setData({
waterfallarr: [...this.data.waterfallarr,...newVal]
})
}
}
},
/**
* 组件的初始数据
*/
data: {
itemwidth: '', //节点的宽度
topArr: [], //记录每一列的总高度
allHeightArr: [], // 记录所有节点的高度的数组
allPositionArr: [], // 记录所有节点距离顶部距离的数组
num: 0, // 记录瀑布流多少张图片
oldNum: 0, // 记录瀑布流加载一次后上一次一共有多少张图片
height: 0, // 记录瀑布流的总高度
waterfallarr: [] // 所有图片数据数组
},
具体的逻辑实现
通过image组件的bindload事件获取图片的信息;使用小程序内置Api接口wx.createSelectorQuery()计算每一个图片节点的高度,topArr记录每一列的总高度,allPositionArr记录所有节点距离顶部距离的数组,通过对比每一列的高度选择节点的位置该分部在那一列,节点到顶部的距离则为该节点当前这一列所有节点的高度的总和,计算完总和在topArr内作数据的更新,直到最后一直图片加载完毕。
需要注意的地方:在自定义组件内wx.createSelectorQuery()存在一个指向性问题所以在后面需要加上in(this)重新指向自定义组件才可以获取组件内的元素的高度属性
具体逻辑代码如下:
/**
* 组件的方法列表
*/
methods: {
loadImgFinish(e){
let that = this
let index = e.currentTarget.dataset.index
let dom = wx.createSelectorQuery().in(this)
// 获取所有图片容器的节点信息
dom.select("#waterfall-item-id-" + index).fields({ size: true },(res) => {
that.setData({
num: that.data.num + 1,
['waterfallarr[' + index + '].show']: false, // 这边事先把所有图片隐藏,用于后面做懒加载的效果
['allHeightArr[' + index + ']']: res.height,
})
if(that.data.num == that.data.waterfallarr.length){
for(let i = that.data.oldNum; i < that.data.num; i++){
const getSortMsg = () => {
// 记录各列高度的数据进行重新排序
let sortArr = [...that.data.topArr].sort((a, b) => a - b)
return {
'shortestHeight': sortArr[0],
'longestHeight': sortArr[that.data.col - 1],
'shortestIndex': that.data.topArr.indexOf(sortArr[0]),
'longestIndex': that.data.topArr.indexOf(sortArr[that.data.col - 1])
}
}
const { shortestHeight,shortestIndex } = getSortMsg()
that.setData({
['allPositionArr[' + i + ']']: {
top: shortestHeight + that.data.blockspace,
left: (that.data.itemwidth * shortestIndex ) + that.data.blockspace * ( shortestIndex + 1 )
},
['topArr[' + shortestIndex + ']']: that.data.topArr[shortestIndex] + that.data.allHeightArr[i] + that.data.blockspace
})
// 瀑布流懒加载
that.createIntersectionObserver().relativeToViewport({ bottom: 50 }).observe('#waterfall-item-id-' + i ,(res) => {
if(res.intersectionRatio > 0){
that.setData({
['waterfallarr[' + i + '].show']: true
})
}
})
}
that.setData({
'oldNum': that.data.num,
'height': Math.max.apply(null,that.data.topArr) + that.data.blockspace
})
// 瀑布流加载完成后,通知父组件加载完毕
that.triggerEvent('waterFallResh', { loading: true })
}
}).exec()
},
// 下拉刷新初始化数据
refresh(){
for(let i = 0; i < this.data.col; i++){
this.data.topArr.push(0)
}
this.setData({
'num': 0,
'oldNum': 0,
'height': 0
})
},
}
组件内slot插槽的使用,来自定义图片信息
<view class="water-content">
<slot name="slot{{ index }}"></slot>
</view>
使用index做为唯一的标识
在需要瀑布流插件中直接在组件内编辑自定义的代码内容,插槽的属性名需要一一对应
<waterfall
waterfall="{{ waterfall }}"
col="2"
blockspace="5"
bind:waterFallResh="loadFinish"
>
<view wx:for="{{ waterfall }}" wx:key="this" slot="slot{{ index }}">
<view class="title">{{ item.title }}</view>
<view class="basic-info">
<view class="basic-info-userinfo">
<image src="{{ item.headimgurl }}" mode="widthFix"></image>
<view class="user-name">{{ item.userName }}</view>
</view>
<view class="evaluation">好评:{{ item.thumbs_num }}</view>
</view>
</view>
</waterfall>
到此瀑布流布局就实现了。