微信小程序的无限瀑布流实现总算做完了,换了好几种方法,过程中出现了各种BUG。
首先官方有瀑布流的插件(Skyline /grid-view),不是原生的我就不想引入,因为我的方块流页面已经搭好了,引入说不定就要涉及样式的修改、代码量的增大等麻烦问题。
H5我虽然也做了瀑布流,但是是用绝对定位来做的,性能消耗有点大。所以小程序这边就是想把原本flex固定宽高的改成两列。(纯CSS就不要想了,根本不可能实现,虽然也查到了新的css瀑布流规则,但绝大部分浏览器不支持也是白搭。)
方案一:
const query = wx.createSelectorQuery().in(this);
query.select(`.desc-${index}`).boundingClientRect((res) => {
}).exec();
原本显示的数据先做隐藏,之后利用boundingClientRect来获取隐藏块的高度,再计算对比高度看应该放入list1还是list2中。
这样做会出现两个问题,第一,新增列会明显抖动,因为image我用了mode="widthFix",加载页面跳一下还好,无限瀑布流是要一直可以下拉加载的,就会导致每次新图出来都要跳一下,体验很差……
即使第一个问题我可以缓存高度,替换为aspectFill解决。但第二个问题是,它加载越多,异步延迟就越大,会导致后面的高度根本获取不到而无法正常排版(大概超过80条左右?)……
方案二:
使用wx.getImageInfo来代替获取图片高度,但这样会出现一个明显的问题,就是每次图片的请求是翻倍的,图片本身就加载请求了一次,现在还多用接口请求了一次,这样做太消耗性能了……
另外要注意的是,我的文字其实也是不定高度的,所以不能只对比图片,还得把图片以外的因素都加进来才行。
方案三:
隐藏块使用bindload获取图片高度。这样既避免了方案一中后续获取不到高度,又使得图片不需要二次请求。于是顺着这个思路走,最终解决了这个浪费了我整整一天工作日的瀑布流问题。
index.wxml
<template name="card">
<navigator class="recommend-card" url="/pages/trade/detail/index?id={{item.id}}">
<view class="recommend-top">
<image wx:if="{{item.height}}"
class="recommend-img"
src="{{ item.pic }}"
mode="aspectFill"
style="height: {{item.height}}rpx;" />
<image wx:else
class="recommend-img"
src="{{ item.pic }}"
data-index="{{index}}"
bindload="imgLoad"
data-title="{{item.title}}"
mode="widthFix" />
</view>
<view class="aui-padded-10 recommend-bottom">
<view class="aui-font-size-14 recommend-title">{{item.title}}</view>
<view class="aui-font-size-12 c9 recommend-position">
<image alt="地点" class="position-img" src="{{locUrl}}/svg/trade-position.svg" />
<view class="no-wrap">{{item.province}} {{item.city}}</view>
</view>
</view>
</navigator>
</template>
<view class="recommend-list">
<template is="card" wx:for="{{tradeList}}" wx:key="index" data="{{item, locUrl, index}}"></template>
</view>
<view class="flex-b">
<view>
<template is="card" wx:for="{{list1}}" wx:key="index" data="{{item, locUrl, index}}"></template>
</view>
<view>
<template is="card" wx:for="{{list2}}" wx:key="index" data="{{item, locUrl, index}}"></template>
</view>
</view>
index.js
import { dealTradePic } from "~/utils/trade";
Component({
options: {
addGlobalClass: true,
},
properties: {
goodsList: {
type: Array,
value: [],
observer(goodsList) {
const tradeList = dealTradePic(goodsList);
const obj = { tradeList };
if (tradeList.length === 10) {
// 因为没有置0处理,重新加载时前10条不会触发onload
const { list1, list2, bufferH1, bufferH2 } = this.data;
obj.list1 = list1.slice(0, 10);
obj.list2 = list2.slice(0, 10);
obj.h1 = bufferH1;
obj.h2 = bufferH2;
}
this.setData(obj);
},
}
},
data: {
locUrl: getApp().locUrl,
tradeList: [],
h1: 0,
h2: 0,
bufferH1: 0,
bufferH2: 0,
list1: [],
list2: [],
count: 0,
},
methods: {
imgLoad(e) {
const { width, height } = e.detail;
const { index, title } = e.currentTarget.dataset;
const { tradeList, list1, list2, h1, h2 } = this.data;
// 高度比例切换
let h = 340 * height / width;
h = h > 480 ? 480 : h;
tradeList[index].height = h;
// 增加文字与其他高度
const word = title.replace(/[^\x00-\xff]/g, "aa").length;
const wh = parseFloat((h + (word > 22 ? 38 : 0)).toFixed(2)) + 150;
if (h1 <= h2) {
list1.push(tradeList[index]);
this.data.h1 = parseFloat(h1.toFixed(2)) + wh;
} else {
list2.push(tradeList[index]);
this.data.h2 = parseFloat(h2.toFixed(2)) + wh;
}
// 初始高度记录,用于清空
this.data.count++;
if (this.data.count === 10) {
this.data.bufferH1 = this.data.h1;
this.data.bufferH2 = this.data.h2;
}
this.setData({ list1, list2 });
}
}
});
index.wxss
.recommend-list {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
padding: 0;
height: 0;
overflow: hidden;
}
.recommend-card {
box-sizing: border-box;
font-size: 24rpx;
border-bottom: none;
width: 342rpx;
margin-bottom: var(--gap);
}
.recommend-top {
width: 340rpx;
}
.recommend-img {
display: block;
width: 100%;
height: 100%;
border-radius: 16rpx 16rpx 0 0;
overflow: hidden;
}
.recommend-title {
font-size: 28rpx;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.recommend-position {
display: flex;
align-items: center;
margin-top: 12rpx;
}
.recommend-bottom {
background-color: white;
border-radius: 0 0 16rpx 16rpx;
}