首先声明虽然本篇是写的微信小程序的案例,但是也可用于H5,思路是想通的,只是有些api的差异,最后会贴代码片段
/**
* 处理占位元素,就是在获取新的数据后
* 通过SelectQuery获取当前数据的实际高度,然后把这个高度设置到占位元素上
*/
getCurrentItemHeight() {
const query = this.createSelectorQuery();
const { virtualId } = this.data
query.select(`#${virtualId}`).boundingClientRect()
query.exec((res) => {
this.setData({
height: res[0].height
}, this.observePage())
})
}
/**
* 监听元素与页面的相交
* 可以选择指定元素为参照区域,也可以选择页面为参照元素,只是API不同
* @doc https://developers.weixin.qq.com/miniprogram/dev/api/wxml/IntersectionObserver.html
*/
observePage() {
const { virtualId, observeDistance, wrapId } = this.data
let IntersectionObserver = wx.createIntersectionObserver(this);
(wrapId ? IntersectionObserver.relativeTo(`#${wrapId}`) : IntersectionObserver.relativeToViewport({ top: observeDistance, bottom: observeDistance }))
.observe(`#${virtualId}`, ({ intersectionRatio }) => {
this.setData({
isShow: intersectionRatio > 0,
})
})
}
<view id="{{virtualId}}">
<block wx:if="{{isShow}}">
<slot></slot>
</block>
<view wx:else style="height: {{ height }}px"></view>
</view>
/**
* 获取列表数据
* @describe 瀑布流处理,哪列高度小,就往哪列push新数据
*/
getList() {
let { listQuery: { pageIndex, pageSize }, columns, columnsHeight } = this.data;
for (let i = 0; i < pageSize; i++) {
const height = Math.floor(Math.random() * 100)
const item = height < 50 ? height + 50 : height
const position = this.computePosition(columnsHeight)
columns[position].push(item)
columnsHeight += item
}
// 在html中双重遍历columns,然后通过flex:1均匀分布
this.setData({
columns,
})
this.data.columnsHeight = columnsHeight
}
/**
* 获取高度最小列下标
*/
computePosition(heights) {
const min = Math.min(...heights);
return heights.findIndex((item) => item === min)
}
margin-top
移动列元素达到视觉上的瀑布流衔接效果getList() {
let { listQuery: { pageIndex }, column, columnsHeights } = this.data;
const columns = [];
// 上一组的高度数据,用于计算偏移值
const lastHeights = [...columnsHeights];
// 获取数据
const list = this.getListData();
// 初始化当前屏数据
for (let i = 0; i < column; i++ ) {
columns.push([]);
}
// 遍历新数据,分配至各列
for (let i = 0; i < list.length; i++) {
const position = this.computePosition(columnsHeights);
columns[position].push(list[i]);
columnsHeights[position] += Number(list[i].height);
}
this.setData({
[`listData[${pageIndex}]`]: {
columns,
columnOffset: this.computeOffset(lastHeights),
}
});
this.data.listQuery.pageIndex = pageIndex + 1;
this.data.columnsHeights = columnsHeights;
},
/**
* 获取列表数据
*/
getListData() {
const result = []
for (let i = 0; i < this.data.listQuery.pageSize; i++) {
const height = Math.floor(Math.random() * 300);
const item = {
height: height < 150 ? height + 150 : height,
color: this.randomRgbColor(),
};
result.push(item);
}
return result;
},
/**
* 随机生成RGB颜色
*/
randomRgbColor() {
var r = Math.floor(Math.random() * 256); //随机生成256以内r值
var g = Math.floor(Math.random() * 256); //随机生成256以内g值
var b = Math.floor(Math.random() * 256); //随机生成256以内b值
return `rgb(${r},${g},${b})`; //返回rgb(r,g,b)格式颜色
},
/**
* 获取最小高度列下标
*/
computePosition(heights) {
const min = Math.min(...heights);
return heights.findIndex((item) => item === min);
},
/**
* 计算偏移量
*/
computeOffset(heights) {
const max = Math.max(...heights);
return heights.map((item) => max - item);
},
onScrollLower() {
this.getList();
}
<view>
<scroll-view class="virtualScrollView" eventhandle scroll-y bindscrolltolower="onScrollLower">
<block wx:for="{{ listData }}" wx:key="screenIndex" wx:for-index="screenIndex" wx:for-item="screenItem">
<VirtualItem virtualId="virtual_{{pageIndex}}">
<view class="fall">
<block wx:for="{{ screenItem.columns }}" wx:key="columnIndex" wx:for-index="columnIndex" wx:for-item="column" >
<view style="margin-top: -{{screenItem.columnOffset[columnIndex]}}px;" class="fallCol">
<view wx:for="{{column}}" style="height: {{ item.height }}px; background-color: {{ item.color }};" wx:key="index" wx:for-item="item" wx:for-index="index">
screen: {{ screenIndex }}, column: {{ columnIndex }}
</view>
</view>
</block>
</view>
</VirtualItem>
</block>
</scroll-view>
</view>