实现类似小红书首页照片墙瀑布流的效果
1.实现左右两侧布局
2.获取图片高度动态插入高度小的一列
3.长列表滚动优化
1.0版本
页面左右两侧布局
<scroll-view
scroll-y='true'
class='category-wrapper'
bindscrolltolower="scrolltolower"
threshold="100"
>
<view class="fall">
<view class="fall-column" wx:for="{{picList}}" wx:for-item="column" wx:key="index">
<view class="fall-column-item" wx:for="{{column}}" wx:key="index" wx:for-item="item">
<image
lazy-load
class="fall-column-item-img"
src="{{item.imgUrl}}"
data-url="{{item.imgUrl}}"
data-id="{{item.id}}"
mode="widthFix"
bindtap="preview"
>
</image>
<view bind:longpress="toCopy" data-text="{{item.description}}" wx:if="{{item.description}}">{{item.description}}</view>
</view>
</view>
</view>
</scroll-view>
2.通过wx.getimageinfo获取图片信息,图片高度
用一个长度为2的数组heightArr记录已经加载的图片的高度,比较第1项和第2项的大小,然后把新的图片的高度累加到较小的一项上。
import api from '../../api/index'
Page({
data: {
list: [],
heightArr: []
},
async onLoad () {
let {results} = await api.fetchImages()
let col = 2
for (let i in results) {
results[i].cover = results[i].imageUrl
// 获取图片信息
let info = await this.loadImage(results[i].cover)
results[i].height = 165 / info.width * info.height
if (i < col) {
this.data.list.push([results[i]])
this.data.heightArr.push(results[i].height)
} else {
let minHeight = Math.min.apply(null, this.data.heightArr)
let minHeightIndex = this.data.heightArr.indexOf(minHeight)
this.data.list[minHeightIndex].push(results[i])
this.data.heightArr[minHeightIndex] += results[i].height
}
}
this.setData({
list: this.data.list
})
},
loadImage (cover) {
return new Promise(resolve => {
wx.getImageInfo({
src: cover,
success: (res) => {
resolve(res)
}
})
})
}
})
通过添加伪元素过渡图片加载显示的效果
.fall-column-item::after {
content: '加载中';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: inline-block;
color: #666;
}
wx.getImageinfo接口会先把图片下载到本地,一次下载量上限是10,由于多了本地路径的时间,所以图片加载的速度较慢。
2.0
渲染层优化,由于数据是分页加载的,在渲染层上通过一个隐藏的hidden节点,image标签的bindload事件来获取图片的高度,速度有明显提升。
优点:载入速度加快了,不必要的图片下载请求减少了
缺点:渲染了不必要的节点
长列表的优化,滚动加载数据,节点会不断增加,在1.0的版本上,数据是一维数组,每次更新,随着数据量的加大,数组会越来越长。可以改成二维数组,每次仅更新数组的某一项。但是节点的数量并没有减少,因此,参考微信官方recycle-view的思想,https://developers.weixin.qq....,进行长列表的优化,在滚动过程中,只渲染固定页数的节点,将其余的节点,留一个空的节点占位符,为防止白屏和页面抖动的问题,给空节点一个页面内容数据的高度。
核心思想
1.由于是左右两列布局,所以数组的是[[column1], [column2]]的结构
2.由于要分页加载,所以要获取每一页数据渲染在页面的内容的高度,通过计算滚动的距离scrollTop + windowHeight(区域的高度)和记录每一页数据的高度的数组pageHeightArr来比较,当前应该渲染的是第几页的数据,这时数据结构变成三维数组[[[page1], [page2]], [[page1], [page2]]]
注意:在初始化后滚动时获取内容区高度时,在滚动过程中可能会出现获取内容区高度时0的情况,所以,后期改成在图片加载成功后,获取内容区的高度。
渲染层代码
<scroll-view
id="contain"
scroll-y='true'
class='category-wrapper'
bindscrolltolower="scrolltolower"
bindscroll="scroll"
threshold="100"
>
<view class="wrap">
<view wx:for="{{finList}}" wx:for-index="index1" wx:for-item="column">
<block wx:if="{{column.length > 0}}">
<view id="wrap{{index1}}_{{index}}" wx:for="{{column}}" wx:key="index" wx:for-item="item">
<view wx:for="{{item}}" wx:key="index" wx:for-item="el">
<image
lazy-load
src="{{el.imgUrl}}"
data-url="{{el.imgUrl}}"
data-id="{{el.id}}"
mode="widthFix"
bindtap="preview"
>
</image>
<view bind:longpress="toCopy" data-text="{{el.description}}" wx:if="{{el.description}}">{{el.description}}</view>
</view>
<view class="placeHolder" v-else style="height: {{item.height}}px"></view>
</view>
</block>
</view>
</view>
</scroll-view>
<view class="hidden">
<block wx:for="{{hiddenList}}" wx:key="index">
<image src="{{item.imgUrl}}" data-index="{{index}}" bindload="imgLoad" binderror="imgError"></image>
</block>
</view>
逻辑层代码
在一页的数据加载完成后,用wholeList记录全量数据,finList记录渲染的数据,heightArr记录每一页完成后,下一页的第一项数据应该插入高度较小的那一列。
async allLoad () {
let fin = true
for (let i = 0; i < this.data.hiddenList.length; i++) {
if (!this.data.hiddenList[i].load) {
fin = false
break
}
}
if (fin) {
let arr = [[], []]
let height = this.data.heightArr
for (let i = 0; i < this.data.hiddenList.length; i++ ) {
if (i < 2 && !height[i]) {
arr[i].push(this.data.hiddenList[i])
height[i] = this.data.hiddenList[i].height
} else {
let min = height[0] <= height[1] ? 0 : 1
height[min] += this.data.hiddenList[i].height
arr[min].push(this.data.hiddenList[i])
}
}
for (let i = 0; i < arr.length; i++) {
this.data.finList[i][this.data.index] = arr[i]
}
this.getwrapHeight()
this.setData({
finList: this.data.finList,
wholeList: JSON.parse(JSON.stringify(this.data.finList)),
heightArr: height
})
}
},
获取每一次加载后,左右两列内容的高度
async getwrapHeight () {
setTimeout(async () => {
const contain1 = await createSelectorQuery.call(this, `#wrap0_${this.data.index}`);
const contain2 = await createSelectorQuery.call(this, `#wrap1_${this.data.index}`);
// console.log('左侧' + contain1.height)
// console.log('右侧' + contain2.height)
this.setData({
['pageHeightArr[' + this.data.index + ']']: Math.max(contain1.height, contain2.height)
})
}, 500)
},
滚动过程中始终只渲染最大固定2页的数据,(例如在加载第4页时,应该将第1页和第二页的节点数换成只有固定高的空节点)
scroll (e) {
this.$throttle(() => {
let height = 0
for (let i = 0; i <= this.data.pageHeightArr.length; i++) {
height += this.data.pageHeightArr[i]
if (height > e.detail.scrollTop + this.data.windowHeight) {
this.setData({
curIndex: i
})
break
}
}
let tempList = new Array(this.data.index + 1).fill(0); //
tempList.forEach((item, i) => {
if (i >= this.data.curIndex - 1 && i <= this.data.curIndex + 1) {
for (let col = 0; col < 2; col++) {
this.data.finList[col][i] = this.data.wholeList[col][i]
}
} else {
this.data.finList[0][i] = this.data.finList[1][i] = {height: this.data.pageHeightArr[i]}
}
})
this.setData({
finList: this.data.finList
})
})
},
效果如图
可以看到,wrap1_0和wrap_1里由占位节点给了内容高度撑开。
总结
1.在1.0的版本上获取图片高度的方案上,选择wx.getimageinfo的方式并不是一个好方法,由于图片多了本地路径的下载,所以加载速度较慢,有明显的加载等待时间。所以在2.0里通过image的bindload来获取高度,需要注意,也需要有图片加载失败的binderror的处理。
2.瀑布流抽象出来其实也是长列表,在长列表的优化上,可以将一维数组改成二维数组的方式,来减少逻辑层和渲染层的通信。在无限滚动加载中,可以通过滚动距离的计算,只渲染固定几页的数据,减少在加载过程中不断增加渲染节点,不断通信带来的性能问题。
3.如果是固定item的长列表,可直接使用微信官方的recycle-view插件来做性能优化。不定高瀑布流等长列表,核心思想也是相同的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。