概述
git仓库地址:https://github.com/kapeter/mpMasonry
前段时间,接到一个需求,要在小程序中实现不定高度的瀑布流布局。我首先去万能的百度上搜索了一波,确实有很多方案,但都是固定高度的,这和需求不符。于是决定自己写一个,考虑到后面也会有类似的需求,干脆做成一个通用组件,方便使用。
瀑布流是比较流行的一种网站页面布局,尤其在mobile端经常被用来展示信息流。前段时间,接到一个需求,要在小程序中实现不定高度的瀑布流布局。我首先去万能的百度上搜索了一波,确实有很多方案,但都是固定高度的,这和需求不符。于是决定自己写一个,考虑到后面也会有类似的需求,干脆做成一个通用组件,方便使用。
本套方案主要使用flex模型,结合小程序的特性(boundingClientRect、抽象节点),实现瀑布流布局和组件化。
组件布局
由于手机宽度的限制,一般移动端的瀑布流只有两列,不需要考虑多列的情况,因此我们的布局完全可以通过CSS3的flex模型完成。
<!-- masonry.wxml -->
<view class="masonry-list">
<view class="masonry-list-left" style="{{ 'margin-right:' + intervalWidth }}">
<view id="left-col-inner">
<block wx:for="{{items}}" wx:key="{{item.id}}">
<masonry-item wx:if="{{item.columnPosition === 'left'}}" item="{{item}}"></masonry-item>
</block>
</view>
</view>
<view class="masonry-list-right">
<view id="right-col-inner">
<block wx:for="{{items}}" wx:key="{{item.id}}">
<masonry-item wx:if="{{item.columnPosition === 'right'}}" item="{{item}}"></masonry-item>
</block>
</view>
</view>
</view>
// masonry.wxss
.masonry-list {
width: 100%;
display: flex;
box-sizing: border-box;
}
.masonry-list-left, .masonry-list-right {
flex: 1;
}
flex为1,给予左右两列相同的宽度,通过设置properties中的intervalWidth来控制两者间的间距。
有人可能会奇怪,为什么要在class="masonry-list-left"
的view之后再加一个层级?同一层级会怎么样呢?
来看两个写法的对比图,我们统一给左边加上高度150px,右边高度设为auto。 很明显,在同一层级情况下,右边高度也变成了150px,与左边一致,这会导致我们后面获取两边高度的时候拿到一样的数值,就无法判断该把元素放在哪边。因此,我们要多增加一个层级。
出现这种情况的原因是:flex的column会进行高度补全,和父容器保持一致。
渲染函数
基本布局已经完成,接下来就是要让布局“流”起来。
先来看一下传统瀑布流的原理:先通过计算出一排能够容纳几列元素,然后寻找各列之中所有元素高度之和的最小者,并将新的元素添加到该列上,如此循环下去,直至所有元素均能够按要求排列为止。
根据上述原理,渲染流程如下:
/**
* 渲染函数
*
* @param {Array} items
- 正在渲染的数组
* @param {Number} i
- 当前渲染元素的下标
* @param {Function} onComplete - 完成后的回调函数
*/
_render (items, i, onComplete) {
if (items.length > i && !this.data.stopMasonry) {
this.columnNodes.boundingClientRect().exec(arr => {
const item = items[i]
const rects = arr[0]
const leftColHeight = rects[0].height
const rightColHeight = rects[1].height
this.setData({
items: [...this.data.items, {
...item,
columnPosition: leftColHeight <= rightColHeight ? 'left' : 'right'
}]
}, () => {
this._render(items, ++i, onComplete)
})
})
} else {
onComplete && onComplete()
}
}
为了满足item高度是动态的场景,需要将渲染函数设置为递归函数。以下是渲染函数的执行流程:
- 判断下标,如果递归结束,调用完成回调函数(onComplete),函数结束,反之执行下面流程;
- 通过
boundingClientRect()
调取两边的高度; - 比较两边高度,将结果赋给
columnPosition
字段; - 调用
setData()
将新的元素渲染到dom上,新的元素位置基于columnPosition
字段的值; - 渲染完成后,在
setData()
的回调函数中执行下一层递归,确保下一次boundingClientRect()
能获取到最新的高度。
setData()
为异步渲染,详细说明请见小程序指南-双线程下的界面渲染- 由于每渲染一个元素,需要执行一次
boundingClientRect()
和setData()
,渲染时间较长。exec()
返回的是按请求次序构成的结果数组,即使只执行了一次请求,结果也位于res[0]而不是res。- boundingClientRect的详细用法可查看小程序文档-WXML节点信息API。
刷新函数
有了核心的渲染函数,我们还要进行一些处理。
/**
* 刷新瀑布流
*
* @param {Array} items - 参与渲染的元素数组
*/
_refresh(items) {
const query = wx.createSelectorQuery().in(this)
this.columnNodes = query.selectAll('#left-col-inner, #right-col-inner')
return new Promise((resolve, reject) => {
this._render(items, 0, () => {
resolve()
})
})
}
_refresh
函数包括两部分:
- 获取左右两列的WXML节点(这一步放在渲染函数中,会重复获取,影响性能)
- 返回一个Promise对象,将
_render
函数包起来,并在_render
的完成回调函数中触发resolve()
,这样就能在渲染结束后执行其他操作。
使用抽象节点剥离业务逻辑
当前存在一个问题,masonry-item组件是用来承载元素的业务逻辑,如果项目存在多处需要瀑布流,并且业务逻辑不一样,那就需要修改masonry组件,添加判断条件,这就产生了耦合,不符合通用组件的规范。因此,我们需要进行解耦。
这里需要用到“抽象节点”。以下是定义:自定义组件模版中的一些节点,其对应的自定义组件不是由自定义组件本身确定的,而是自定义组件的调用者确定的。这时可以把这个节点声明为“抽象节点”。
简单来说,就是在masonry组件内部定义抽象节点masonry-item,这个节点可以代表任何组件,只有当页面调用masonry组件时,这个组件才被确定,这样就能将业务逻辑组件剥离出来了。
具体实现很简单,在masonry组件声明抽象节点。
// masonry.json
"componentGenerics": {
"masonry-item": true
}
在页面调用时,指定该抽象节点为哪个组件。
<!-- index.wxml -->
<!-- 指定抽象节点为img-box组件 -->
<masonry generic:masonry-item="img-box""></masonry>
注意点:节点的 generic 引用 generic:xxx="yyy" 中,值 yyy 只能是静态值,不能包含数据绑定。因而抽象节点特性并不适用于动态决定节点名的场景。
如何在页面中使用组件
1、将components目录下中masonry文件夹复制到自己项目中。
2、添加业务组件,并在业务组件中添加property,用于承载数据
// property名必须为item
properties: {
item: {
type: Object
}
}
3、引入masonry组件和所需的业务组件
// index.json
"usingComponents": {
"masonry": "../../components/masonry/masonry",
"img-box": "../../components/img-box/img-box"
}
4、在wxml加入masonry节点
<!-- index.wxml -->
<masonry generic:masonry-item="img-box" id="masonry" interval-width="20rpx"></masonry>
generic:masonry-item
用于指定业务组件,interval-width
为左右两列空隙宽度。
5、调用函数,渲染瀑布流
_doStartMasonry(items) {
// 通过ID,获取组件实例
this.masonryListComponent = this.selectComponent('#masonry');
// 调用组件的start函数,渲染瀑布流
this.masonryListComponent.start(items).then(() => {
console.log('render completed')
})
}
为保证页面显示效果,建议一次渲染不超过100个元素。
方法列表
函数名 | 函数功能 | 参数说明 | 返回值 |
---|---|---|---|
append | 批量添加元素 | {Array} items - 新增的元素数组 | Promise |
delete | 批量删除瀑布流中的元素 | {Number} start - 开始下标 {Number} end - 结束下标 | 无 |
deleteItem | 删除瀑布流中的某个元素 | {Number} index - 数组下标 | 无 |
start | 启动组件,开始渲染瀑布流 | {Array} items - 参与渲染的元素数组 | Promise |
stop | 停止渲染瀑布流,清空数据 | 无 | 无 |
updateItem | 更新渲染数组中的某个元素 | {Object} newItem - 修改后的元素 {Number} index - 需要更新的数组下标 | 无 |
实践案例
原文https://www.kapeter.com/post/64
最后
以上就是畅快柠檬为你收集整理的小程序瀑布流组件组件布局渲染函数刷新函数使用抽象节点剥离业务逻辑如何在页面中使用组件方法列表实践案例的全部内容,希望文章能够帮你解决小程序瀑布流组件组件布局渲染函数刷新函数使用抽象节点剥离业务逻辑如何在页面中使用组件方法列表实践案例所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复