原生微信小程序之瀑布流布局&上拉加载

639 阅读5分钟

手把手写教程、复制粘贴cv搞定

本地demo,逻辑清晰,方便复制

目录结构 & 目标效果

image.png

每个瀑布流卡片的布局结构:

image.png

最终效果:

1.gif

步骤

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

新人写文章~有什么问题和建议请在评论区留言,欢迎指教!