什么是瀑布流布局
瀑布流布局,一般等宽,不等高的列表排列
原理是找出高度之和最小的那一列,在高度最小列继续添加元素
可以通过 absolute
定位实现,动态计算每一项的 top
和 left
封装瀑布流方法
function getAllRect(context, selector) {return new Promise(function (resolve) {wx.createSelectorQuery().in(context).selectAll(selector).boundingClientRect().exec(function (rect) {if (rect === void 0) {rect = []}return resolve(rect[0])})})
}/*** 瀑布流* @param {*} context 页面或组件this对象* @param {string} selector 选择器* @param {Object} options* @param {number=375} options.width 屏幕宽度* @param {number=2} options.column 列数* @param {number|string} options.gap 每列直接的间隙* @param {number=0} options.padding 整个列表左右的padding* @param {number=0}* @returns {Array} 计算每项的top、left、height的数组*/
async function waterFall(context, selector, options = {}) {let items = await getAllRect(context, selector)if (items.length <= 0) return []let { gap = 15, column = 2, padding = 0, width = 375, firstColumnToTop = 0 } = options// 1- 确定列数 = 页面的宽度 / 图片的宽度,单例的宽度let itemWidth = items[0].width// 定义每一列之间的间隙 pxif (gap === 'auto') {gap = (width - itemWidth * column) / (column - 1)}let _columnHeightArr = [] // 保存每列高度let result = []for (let i = 0, len = items.length; i < len; i++) {if (i < column) {// 2- 确定第一行let top = firstColumnToToplet left = (itemWidth + gap) * i + padding// 瀑布流列表左右paddingif (i === 0 || i === len - 1) {left = padding}_columnHeightArr.push(items[i].height - top)result.push({top,left,height: items[i].height,})} else {// 其他行// 3- 找到数组中最小高度 和 它的索引let minHeight = Math.min(..._columnHeightArr)let minIndex = _columnHeightArr.findIndex((item) => item === minHeight)// 4- 设置下一行的第一个盒子位置// top值就是最小列的高度 + gapresult.push({top: _columnHeightArr[minIndex] + gap,left: result[minIndex].left,height: items[i].height,})// 5- 修改最小列的高度// 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 间隙的高度_columnHeightArr[minIndex] = _columnHeightArr[minIndex] + items[i].height}}return result
}
使用
vim demo.wxml
<!--需要子元素撑大父元素高度的情况,才需要设置height--><view class="goods_list flex flex-wrap relative" style="width:100%;height:{{height}}"><block wx:for="{{list}}" wx:key="index"><view class="goods_item" style="position:absolute;top:{{ item.top }}px;left:{{ item.left }}px"bindtap="handleItem" data-item="{{item}}"><view class="goods_img"><goodsImage detail="{{ {imgUrl:item.goodsIcon} }}" isAllowCash="{{true}}"></goodsImage></view><view class="goods_mes"><view class="goods_name g-t-over2">{{item.goodsName}}</view><view class="goods_price">{{item.price}}</view></view></view></block></view>
vim demo.js
// 需要在节点加载到页面后调用
onReady() {const { screenWidth, list } = this.datawaterFall(this, '.goods_item', { width: screenWidth, gap: 'auto' }).then((arr) => {if (!arr.length) returnlet lastNode = arr[arr.length - 1]let height = lastNode.top + lastNode.height + 'px'this.setData({list: list.map((item, index) => ({ ...item, ...arr[index] })),height,})})
}
效果
小结
既然都封装成函数了,为什么不封装成组件调用呢?
组件调用可以参考这个小程序的瀑布流组件me-waterfall
我看了这个组件的源码,用到组件间关系
来实现瀑布流结构,整个瀑布流组件内部也需要父子组件关系,父组件监听子组件插入元素,获取元素的动态高度,往高度最小列添加新元素。
但是我引入使用,在组件内使用me-waterfall
组件,组件间关系方法 linked
不生效,官方论坛也找不到原因,便弃用。
类似这样的结构:
<!--page.wxml-->
<view><goodsList />
</view<!--goodsList.wxml-->
<view><me-waterfall><me-waterfall-item wx:for="{{list}}" wx:key="index"><image src="{{item.imgUrl}}" style="width:100%;height:{{item.height}}px" /></me-waterfall-item></me-waterfall>
</view
于是,自己封装一个方法使用。便有了此文。
封装成方法也有优点,不需要引入组件,简单引入一下方法调用即可。