最近在做一个小程序项目,遇到如题问题,在网上各种搜索解决方案不如人意,什么flex布局、多列布局、cloumn诸如此类都达不到需求。于是决定自己炸馍炸馍。最终有情人终成眷属(什么鬼= =..)
效果如图: timg1.png首先我们需要考虑几个问题
1 小程序没有类似H5简单粗暴直接操作dom的api。
2 瀑布流和无限加载问题(绑定关系)
解决以上问题之后,就可以一路向西了。
我们先讨论第一个问题,操作dom无非是想直接获取dom信息,width、height等等,然后开始布局,如果获取到这些瀑布流布局问题迎刃而解。
解决思路如下
1 首页是一堆list列表组件,每个组件展示一页数据,因此我们页面获取的数据需要是二维数组, 这么做的好处是当数据量过大时,避免小程序赋值操作this.setData的大小限制1024KB。
timg6.png
2 每一个list开始获取image相关信息,通过小程序image组件bindload事件获取,然后计算元素定位位置top值、left值,使用定位absolute,然后计算list列表最外层view(position;relative)实际高度,完成后页面就由每10条数据为一页、一页一页的展示。这样处理会有一个需要注意的地方,上一页列表产生的高度差需要计算出来传递个下一页列表计算定位问题,我们通过主页面为桥梁传递上一个列表所产生的dValue高度差。
timg5.png
index.wxml:
<!-- 列表 -->
<list wx:for="{{list}}" d-value="{{dValue}}" list-data="{{item}}" bind:heightlog="heightlog"></list>
<!-- loading -->
<view style='height: 200rpx; text-align: center; line-height: 1; padding: 86rpx 0; font-size: 28rpx; color: #999;'>
<text hidden='{{!isLoading}}'>正在加载...</text>
</view>
其中list为二维数组,item为二维数组子元素[ ]包含10条数据,d-value传递给list组件,告诉当前组件上一个列表产生的高度差,用于当前组件计算元素top值
index.json:
{
"usingComponents": {
"list": "./template/list/"
}
}
index.js:
const app = getApp()
Page({
data: {
list: [], // 模拟数据
dValue: 0, // 列表高度差
isLoading: false // 阻止无限触发加载阈值
},
onLoad: function () {
this.getlist() // 初始化列表数据
},
onReachBottom () { // 触底函数
this.getlist()
},
heightlog (e) {
this.setData({ dValue: e.detail})
},
getlist () {
if (this.data.isLoading) return
this.setData({ isLoading: true })
setTimeout(() => { // 模拟ajax获取数据
this.setData({ isLoading: false })
let imgArr = []
for(var i = 0; i < 10; i++) {
imgArr.push({
imgUrl: '/img/demo' + (Math.ceil(Math.random() * 10)) + '.jpg',
desc: '业精于勤,荒于嬉。行成于思,毁于随。'
})
}
let page = this.data.list.length
let itemlist = 'list[' + page + ']' // 关键点,二维数组渲染组件,每个组件得到10条数据,避免setData数据量过大问题
this.setData({
[itemlist]: imgArr
})
}, 1000)
}
})
完成index页面的数据、以及无限加载之后,重点在与list组件怎么渲染数据了。
话不多说直接上代码:
list.wxml
<!--template/list/list.wxml-->
<image wx:for="{{images}}" data-index='{{index}}' src="{{item.imgUrl}}" hidden bindload="imgOnload"></image>
<view class="img-group" style='height: {{wrapperHeight}}px'>
<view class='img-wrapper' wx:for="{{list}}" wx:key="{{index}}" style="width: calc(50% - {{3*gap/2}}px); top: {{item.top}}px; left: {{item.left}}px">
<image mode='widthFix' src="{{item.imgUrl}}" style="height:{{item.showHeight}}px"></image>
<view class='desc' wx:if="{{item.desc != ''}}">{{item.desc}}</view>
</view>
</view>
list.json:
{
"component": true,
"usingComponents": {}
}
list.wxss:
/* template/list/list.wxss */
.img-group {
position: relative;
}
.img-wrapper {
position: absolute;
font-size: 0;
}
.img-wrapper image {
width: 100%;
height: auto;
border-radius: 10rpx;
margin-bottom: 5px;
}
.img-wrapper .desc {
font-size: 14px;
color: #555555;
line-height: 1;
height: 15px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
list.js:
// index/template/list.js
const app = getApp() // 获取app信息
Component({
// 组件的属性列表
properties: {
listData: {
type: Object,
value: null,
observer: function (newVal, oldVal, changedPath) {
// console.log(newVal)
this.setData({
images: newVal
})
}
},
dValue: {
type: Number,
value: 0,
observer: function (newVal, oldVal, changedPath) {
// console.log(newVal)
if (this.data.HValue !=0) return // 避免重新赋值导致布局变化
this.setData({
HValue: newVal
})
}
}
},
// 组件生命周期函数,在组件布局完成后执行,此时可以获取节点信息
ready () {
this.setData({ windowWidth: app.systemInfo.windowWidth }) // 设置屏幕宽度
},
// 组件的初始数据
data: {
windowWidth: 0, // 屏幕宽度
HValue: 0, // 上一个列表高度差
gap: 15, // 元素之间间隔
images: [], // 获取元素信息
initlist: [], // 判断长度作用,避免获取所有数据没有完成之前开始渲染
list: [], // 真正渲染的处理过的数据
wrapperHeight: 0 // 需要更新的最外层盒子高度
},
// 组件的方法列表
methods: {
imgOnload (e) {
let options = e
let imgItem = {}
let index = options.target.dataset.index // 保证传递过来的数据索引正确
imgItem = this.data.images[index] // 获取item信息
imgItem.width = options.detail.width // 获取图片宽度
imgItem.height = options.detail.height // 获取图片高度
let item = 'initlist[' + index + ']'
this.setData({
[item]: imgItem
})
// 判断长度相等再赋值给真正渲染的数据,避免数据不全就开始渲染
let nums = 0
for (var i = 0; i < this.data.initlist.length; i++) {
if (this.data.initlist[i] == undefined) {
nums++
}
}
if (this.data.initlist.length == this.data.images.length && nums == 0 && this.data.initlist.length > 0) {
this.setData({ list: this.data.initlist })
this.calcOffset()
}
},
calcOffset () {
let list = JSON.parse(JSON.stringify(this.data.list))
let dValue = this.data.HValue // 上个列表产生的高度差
let arr = [0, 0] // 列高度数组, 此案列采用2列
let gap = this.data.gap // item间隙
let otherH = 20 // 其他内容高度 描述信息等等
let itemWidth = (this.data.windowWidth - 3 * gap) / 2 // 元素实际展示宽度
if (dValue > 0) {
arr[1] = -dValue
} else {
arr[0] = dValue
}
for (var i = 0; i < list.length; i++) {
list[i].showHeight = itemWidth * list[i].height / list[i].width // 元素实际展示高度
let minHeight = arr[0] // 找到最小高度
let index = 0
for (var j = 0; j < arr.length; j++) {
if (minHeight > arr[j]) {
minHeight = arr[j]
index = j
}
}
list[i].top = arr[index] // 设置元素top值
list[i].left = index * (itemWidth + gap) + gap // 设置元素left值
list[i].offsetHeight = itemWidth * list[i].height / list[i].width + gap + otherH // 元素总高度
if (list[i].desc == '') {
list[i].offsetHeight -= otherH // 当没有简介内容时去掉高度
}
arr[index] = arr[index] + list[i].offsetHeight // 更新列高度数组
}
let iMax = Math.max(...arr) // 计算列最大值 ,更新最外层盒子高度
this.setData({
list: list,
wrapperHeight: iMax
})
let newDvalue = parseInt(arr[0] - arr[1]) // 产生的新的列表高度差
this.triggerEvent('heightlog', newDvalue) // 子组件通信,告知新的列表高度差,用于新数据计算高度差
}
}
})
以上就是解决小程序瀑布流的方案了,如果小伙伴有更好的方案或者疑问,请联系我。
转载请注明出处,谢谢!
github:https://github.com/lindongxu2017/waterfall
网友评论