手把手写教程、复制粘贴cv搞定
本地demo,逻辑清晰,方便复制
目录结构 & 目标效果
每个瀑布流卡片的布局结构:
最终效果:
步骤
waterFall子组件
wxml结构
<view class='fall-container'>
<!-- 左边一列 -->
<view class='fall-left'>
<block wx:for="{{leftList}}" wx:key="index">
<!-- 瀑布流内容卡片 -->
<view class="item">
<view class="itemTopBlock">
<image lazy-load class="topPhoto" mode="widthFix" src="{{item.cardSrc}}" />
</view>
<view class="itemMain">
<view class="itemTitle">{{item.cardTitle}}</view>
<view class="slipsAddition">
<view class="super">
<view class="bg">YOU享</view>
<view class="num">9折</view>
</view>
<view class="discount">12元2件</view>
<view class="time">1小时达</view>
</view>
<view class="itemContent">
<view class="contentLeft">
<view class="leftPrice">¥{{item.cardPrice}}</view>
<view class="leftSuper">
<image class="superBg" src="../../image/优+.png" />
¥5.5
</view>
</view>
<view class="contentRight">
<view class="rightAdd">+</view>
</view>
</view>
</view>
</view>
</block>
</view>
<!-- 右边一列 -->
<view class='fall-right'>
与左边一列布局完全相同(故可抽离出来为template标签或者子组件)
</view>
</view>
js功能
重点是对父组件传入的数据scrollList做“判断”和“塞入”操作
判断:判断左右两列数组的视图的高度
塞入:数据驱动视图,setData将数据塞入对应的数组中
/**
* 瀑布流组件
*/
var leftList = new Array(); //左侧集合
var rightList = new Array(); //右侧集合
var leftHight = 0,
rightHight = 0,
itemWidth = 0,
maxHeight = 0;
Component({
properties: {
scrollList: {
type: Array,
},
},
data: {
leftList: [], //左侧集合
rightList: [], //右侧集合
leftHight: 0,
rightHight: 0,
itemHeight: 0,
maxHeight: 0,
},
attached: function () {
wx.getSystemInfo({
success: (res) => {
let percentage = 750 / res.windowWidth;//750rpx/屏幕宽度
let margin = 20 / percentage;//计算瀑布流间距
// 拿到瀑布流的宽度itemWidth
itemWidth = (res.windowWidth - margin) / 2;
maxHeight = itemWidth / 0.8;//计算瀑布流的最大高度,防止长图霸屏
},
});
itemWidth);
},
methods: {
/**
* 填充数据
*/
fillData: function (listData) {
for (let i = 0, len = listData.length; i < len; i++) {
const { leftHight } = this.data;
const { rightHight } = this.data;
const { itemHeight } = this.data;
let tmp = listData[i];
tmp.width = parseInt(tmp.width);
tmp.height = parseInt(tmp.height);
tmp.itemWidth = itemWidth;
let per = tmp.width / tmp.itemWidth; //图片宽高比
tmp.itemHeight = tmp.height / per;//image 高度
if (tmp.itemHeight > maxHeight) {
tmp.itemHeight = maxHeight;//image 高度,不超过最大高度
}
if (leftHight == rightHight) {
leftList.push(tmp);
leftHight = leftHight + tmp.itemHeight;
this.setData({leftHight:leftHight})
} else if (leftHight < rightHight) {
leftList.push(tmp);
leftHight = leftHight + tmp.itemHeight;
this.setData({leftHight:leftHight})
} else {
rightList.push(tmp);
rightHight = rightHight + tmp.itemHeight;
this.setData({rightHight:rightHight})
}
}
this.setData({
leftList: leftList,
rightList: rightList,
});
},
},
});
wxss样式
css就是奇淫巧技,内容过多,仅供参考,都是flex布局
.fall-container {
width: 100%;
display: flex;
}
.fall-left {
display: flex;
flex-direction: column;
}
.fall-right {
display: flex;
flex-direction: column;
margin-left: 20rpx;
/* margin-right: 20rpx; */
}
.item {
/* position: relative; */
width: 348rpx;
/* box-sizing: border-box; */
/* display: inline-block; */
padding-bottom: 12rpx;
border-radius: 12rpx;
}
.itemTopBlock {
width: 100%;
border-top-right-radius: 12rpx;
border-top-left-radius: 12rpx;
overflow:hidden;
/* 在父元素设置border-radius和overflow为hidden */
}
.item>.itemTopBlock>.topPhoto {
display: block;
width: 100%;
height: auto;
}
.itemMain {
width: 100%;
height: 250rpx;
background: #fffeff;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
white-space: normal;
border-bottom-right-radius: 12rpx;
border-bottom-left-radius: 12rpx;
justify-content: space-between;
}
.itemMain>.itemTitle {
font-size: 28rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #333333;
white-space: normal;
line-height: 42rpx;
margin: 16rpx 16rpx 8rpx 16rpx;
}
.slipsAddition {
display: flex;
width: 324rpx;
height: 80rpx;
}
.slipsAddition>.super{
display: flex;
justify-content: center;
align-items: center;
width: 112rpx;
height: 26rpx;
border-radius: 4rpx;
opacity: 0.4;
border: 2rpx solid #FF7800;
font-size: 20rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FF7800;
line-height: 20rpx;
margin-right: 16rpx;
}
.super>.bg{
width: 72rpx;
height: 26rpx;
text-align: center;
vertical-align: auto;
line-height: 26rpx;
vertical-align:sub;
background: #f8ecba;
border-radius: 4rpx 0rpx 0rpx 4rpx;
font-size: 20rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #FF7800;
line-height: 26rpx;
}
.slipsAddition>.discount{
width: 82rpx;
height: 26rpx;
border-radius: 4rpx;
opacity: 0.4;
border: 2rpx solid #E72722;
font-size: 20rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #E72722;
line-height: 26rpx;
text-align: center;
margin-right: 16rpx;
}
.slipsAddition>.time{
width: 94rpx;
height: 26rpx;
border-radius: 4rpx;
opacity: 0.39;
border: 2rpx solid #289533;
font-size: 20rpx;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #289533;
line-height: 26rpx;
text-align: center;
}
.itemMain>.itemContent {
display: flex;
width: 100%;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 24rpx;
}
.itemContent>.contentLeft{
display: flex;
flex-direction: column;
margin-left: 16rpx;
}
.itemContent>.contentLeft>.leftPrice {
font-size: 36rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #333333;
line-height: 50rpx;
}
.leftSuper {
display: flex;
align-items: center;
width: 102rpx;
height: 30rpx;
background: #F1E9D2;
border-radius: 2rpx;
font-size: 20rpx;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #956729;
text-align: center;
line-height: 30rpx;
}
.leftSuper>.superBg{
width: 50%;
height: 100%;
margin-left: 0rpx;
}
.itemContent>.contentRight {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
background: red;
display: flex;
justify-content: center;
align-items: center;
margin-right: 24rpx;
}
.contentRight>.rightAdd {
font-size: 50rpx;
color: white;
}
index父组件
wxml结构
引用子组件、绑定数据、添加id
<waterFallView scrollList="{{scrollList}}" id="waterFallView"></waterFallView>
js功能
重点是两个功能:
一是上拉加载(利用page页面的触底事件onReachBottom)
二是模拟异步请求加载瀑布流卡片数据(自定义setScrollList方法封装瀑布流数据操作)
Page({
data: {
scrollList: [],
/**
* 用于控制当 页面 滚动到底部时,显示 “数据加载中...” 的提示,hidden为true则隐藏;为false则显示
*/
hidden: true,
/**
* 数据是否正在加载中,避免用户瞬间多次下滑到底部,发生多次数据加载事件
* 防抖
*/
loadingData: false,
},
onLoad() {
//初始化数据的时候就进行模拟异步请求操作
this.setScrollList();
},
fillData( goods) {
let view = this.selectComponent("#waterFallView");
view.fillData(goods);
},
//setScrollList接口明确职责、处理整合数据
setScrollList() {
// 接口要专一性、封装良好
let { hidden } = this.data,
{ loadingData } = this.data,
{ scrollList } = this.data;
this.setData({loading:true})
wx.showLoading({
title: "商品加载中...",
});
if (hidden) {
this.setData({
hidden: false,
});
}
if (loadingData) {
return; //防止用户多次触底从而触发多次请求
}
// mock假数据
let res = []
//模拟异步请求
setTimeout(() => {
res = [
{
id: 1,
photo: 1,
cardTitle: "卡乐比 热浪薯片香辣味薯片650g 休闲膨化食品",
cardPrice: "19",
cardSrc: "https://picsum.photos/360/400?random=1",
//随机生成网络图片,可定义宽高
width: "360",
height: "400",
itemWidth: "0",
},
{
id: 2,
photo: 2,
cardTitle: "玩具总动员系列盲盒趣味笔0.5mm(黑色)",
cardPrice: "7.5",
cardSrc: "https://picsum.photos/260/360?random=2",
width: "260",
height: "360",
itemWidth: "0",
},
{
id: 3,
photo: 3,
cardTitle: "CLAPOCLAPS 烟管口红4支装粉红炮弹",
cardPrice: "29.9",
cardSrc: "https://picsum.photos/260/460?random=3",
width: "260",
height: "460",
itemWidth: "0",
},
{
id: 4,
photo: 4,
cardTitle: "卡乐比 热浪薯片香辣味薯片650g 休闲膨化食品",
cardPrice: "19",
cardSrc: "https://picsum.photos/360/260?random=4",
width: "360",
height: "260",
itemWidth: "0",
},
];
wx.hideLoading();
this.setData({
scrollList: [...scrollList, ...res],//合并数组比循环setData性能好
hidden: true,
loadingData:false
},
() => {
this.fillData(this.data.scrollList);
});
}, 1000);
},
//onReachBottom触底事件只做调用setScrollList方法的作用,最多加些判断机制
onReachBottom() {
//触底钩子不要做太多冗杂操作,容易触发多次,消耗性能,不易维护
//可以在此判断是否有下一页(防御型编程)
//加载更多假数据
this.setScrollList()
//模拟异步加载更多数据
this.setData({
loadingData: true,
});
},
})
总结
思路总结:
-
封装了一个组件waterFall,专门处理瀑布流布局,重点是外部传入数据的“判断”和“塞入”机制,即判断左右两列数组的视图的高度,根据高度再分别塞入对应的左数组/右数组
-
而外部页面使用触底事件做上拉加载的操作,可结合防抖机制,避免频繁触底而触发事件,即添加个loadingData的布尔值变量:在异步请求中setData数据后设置false,在触底事件发生的函数体中设置为true
新人写文章~有什么问题和建议请在评论区留言,欢迎指教!