封装小程序瀑布流布局组件

封装小程序瀑布流布局组件

最终效果展示如下:
封装小程序瀑布流布局组件_第1张图片

需求分析

1、瀑布流列数随着图片的宽度而计算一屏幕多少列
2、以为两列为例,两列的高度随着图片的插入而改变
3、默认给左右两列插入一张图片,插入图片后,根据两列的的高度做为判断依据,依次给高度更低的一列插入图片
4、使用slot插槽来自定义图片的信息

在component文件夹下新建文件

封装小程序瀑布流布局组件_第2张图片

在需要瀑布流页面的json文件引入组件
封装小程序瀑布流布局组件_第3张图片

第一步、先设置组件wxml布局

<view class="waterfall-container" style="height:{{ height }}px">
  <view 
    class="water-item" 
    wx:for="{{ waterfallarr }}" 
    id="waterfall-item-id-{{ index }}" 
    wx:key="this" 
    style="width:{{ itemwidth }}px;height:auto;top:{{ allPositionArr[index].top }}px;left:{{ allPositionArr[index].left }}px">
    <image 
      class="lazyimg {{ item.show ? 'loadimg' : '' }}" 
      src="{{ item.image_url }}" 
      mode="widthFix" 
      data-index="{{ index }}" 
      bindload="loadImgFinish">image>
    <view class="water-content">
      <slot name="slot{{ index }}">slot>
    view>
  view>
view>

image组件的mode设置为widthFix为了让图片适应容器的宽度并且等比例缩放,绑定bindload事件触发loadImgFinish方法获取图片加载完后的信息

wxss样式如下

page{
  background: #f5f5f5;
}
.waterfall-container{
  position: relative;
  width: 100%;
  height: auto;
  box-sizing: border-box;
  overflow: hidden;
}
.water-item{
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: absolute;
  top: 0;
  left: -50%;          /* 防止一开始图片位置堆积在一个地方 */
  border-radius: 10rpx;
  overflow: hidden;
  background: #cccccc;
}
.water-item image{
  width: 100%;
}
.title{
  padding: 16rpx 16rpx 0 16rpx;
  font-size: 28rpx;
  color: #333333;
  font-weight: 600;
}
.water-content{
  background: #ffffff;
}
.basic-info{
  padding: 16rpx;
  font-size: 28rpx;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.price{
  color: red;
  font-size: 26rpx;
}
.evaluation{
  font-size: 24rpx;
  color: #999999;
}
.lazyimg{
  border-top-left-radius: 14rpx;
  border-top-right-radius: 14rpx;
  transition: opacity .5s ease;
  opacity: 0;
}
.loadimg{
	opacity: 1;
}

组件具体的参数说明

参数说明:
col:为设置的瀑布流列数
blockspace:为图片之间的间距
waterfall:为从父组件获取到的瀑布流数据,当dataFall的数据出现改变时候,在observer:function(){}的异步操作中把数据赋值给waterfallarr

properties: {
    col: {
      type: Number,
      value: 2,
      observer: function(newVal,oldVal){}
    },
    blockspace:{
      type: Number,
      value: 5,
      observer: function(newVal,oldVal){}
    },
    waterfall: {
      type: Array,
      value: [],
      observer: function(newVal,oldVal){
        this.setData({
          waterfallarr: [...this.data.waterfallarr,...newVal]
        })
      }
    }
  },

/**
   * 组件的初始数据
   */
  data: {
    itemwidth: '',   //节点的宽度
    topArr: [],      //记录每一列的总高度  
    allHeightArr: [],     // 记录所有节点的高度的数组
    allPositionArr: [],   // 记录所有节点距离顶部距离的数组
    num: 0,             // 记录瀑布流多少张图片
    oldNum: 0,          // 记录瀑布流加载一次后上一次一共有多少张图片
    height: 0,          // 记录瀑布流的总高度
    waterfallarr: []    // 所有图片数据数组
  },

具体的逻辑实现

通过image组件的bindload事件获取图片的信息;使用小程序内置Api接口wx.createSelectorQuery()计算每一个图片节点的高度,topArr记录每一列的总高度,allPositionArr记录所有节点距离顶部距离的数组,通过对比每一列的高度选择节点的位置该分部在那一列,节点到顶部的距离则为该节点当前这一列所有节点的高度的总和,计算完总和在topArr内作数据的更新,直到最后一直图片加载完毕。

需要注意的地方:在自定义组件内wx.createSelectorQuery()存在一个指向性问题所以在后面需要加上in(this)重新指向自定义组件才可以获取组件内的元素的高度属性

具体逻辑代码如下:

/**
   * 组件的方法列表
   */
  methods: {

    loadImgFinish(e){
      let that = this
      let index = e.currentTarget.dataset.index
      let dom = wx.createSelectorQuery().in(this)
      // 获取所有图片容器的节点信息
      dom.select("#waterfall-item-id-" + index).fields({ size: true },(res) => {
        that.setData({
          num: that.data.num + 1,
          ['waterfallarr[' + index + '].show']: false,    // 这边事先把所有图片隐藏,用于后面做懒加载的效果
          ['allHeightArr[' + index + ']']: res.height,
        })
        if(that.data.num == that.data.waterfallarr.length){
          for(let i = that.data.oldNum; i < that.data.num; i++){
            const getSortMsg = () => {
              // 记录各列高度的数据进行重新排序
              let sortArr = [...that.data.topArr].sort((a, b) => a - b)
              return {
                'shortestHeight': sortArr[0],
                'longestHeight': sortArr[that.data.col - 1],
                'shortestIndex': that.data.topArr.indexOf(sortArr[0]),
                'longestIndex':  that.data.topArr.indexOf(sortArr[that.data.col - 1])
              }
            }
            const { shortestHeight,shortestIndex } = getSortMsg()
            that.setData({
              ['allPositionArr[' + i + ']']: {
                top: shortestHeight + that.data.blockspace,
                left: (that.data.itemwidth * shortestIndex ) + that.data.blockspace * ( shortestIndex + 1 )
              },
              ['topArr[' + shortestIndex + ']']: that.data.topArr[shortestIndex] + that.data.allHeightArr[i] + that.data.blockspace
            })
            // 瀑布流懒加载
            that.createIntersectionObserver().relativeToViewport({ bottom: 50 }).observe('#waterfall-item-id-' + i ,(res) => {
              if(res.intersectionRatio  > 0){
                that.setData({
                  ['waterfallarr[' + i + '].show']: true
                })
              }
            })
          }
          that.setData({
            'oldNum': that.data.num,
            'height': Math.max.apply(null,that.data.topArr) + that.data.blockspace
          })
          // 瀑布流加载完成后,通知父组件加载完毕
          that.triggerEvent('waterFallResh', { loading: true })
        }
      }).exec()
    },

   // 下拉刷新初始化数据
    refresh(){
      for(let i = 0; i < this.data.col; i++){
        this.data.topArr.push(0)
      }
      this.setData({
        'num': 0,
        'oldNum': 0,
        'height': 0
      })
    },   

  }

组件内slot插槽的使用,来自定义图片信息

<view class="water-content">
  <slot name="slot{{ index }}">slot>
view>

使用index做为唯一的标识

在需要瀑布流插件中直接在组件内编辑自定义的代码内容,插槽的属性名需要一一对应

	<waterfall 
      waterfall="{{ waterfall }}"
      col="2"
      blockspace="5"
      bind:waterFallResh="loadFinish"
      >
      <view wx:for="{{ waterfall }}" wx:key="this" slot="slot{{ index }}">
        <view class="title">{{ item.title }}view>
        <view class="basic-info">
          <view class="basic-info-userinfo">
            <image src="{{ item.headimgurl }}" mode="widthFix">image>
            <view class="user-name">{{ item.userName }}view>
          view>
          <view class="evaluation">好评:{{ item.thumbs_num }}view>
        view>
      view>
    waterfall>

到此瀑布流布局就实现了。

你可能感兴趣的:(小程序)