• 小程序瀑布流实现


    什么是瀑布流布局

    瀑布流布局,一般等宽,不等高的列表排列

    原理是找出高度之和最小的那一列,在高度最小列继续添加元素

    可以通过 absolute 定位实现,动态计算每一项的 topleft

    在这里插入图片描述

    封装瀑布流方法

    function getAllRect(context, selector) {
      return new Promise(function (resolve) {
        wx.createSelectorQuery()
          .in(context)
          .selectAll(selector)
          .boundingClientRect()
          .exec(function (rect) {
            if (rect === void 0) {
              rect = []
            }
            return resolve(rect[0])
          })
      })
    }
    
    /**
     * 瀑布流
     * @param {*} context 页面或组件this对象
     * @param {string} selector 选择器
     * @param {Object} options
     * @param {number=375} options.width 屏幕宽度
     * @param {number=2} options.column 列数
     * @param {number|string} options.gap 每列直接的间隙
     * @param {number=0} options.padding 整个列表左右的padding
     * @param {number=0}
     * @returns {Array} 计算每项的top、left、height的数组
     */
    async function waterFall(context, selector, options = {}) {
      let items = await getAllRect(context, selector)
      if (items.length <= 0) return []
      let { gap = 15, column = 2, padding = 0, width = 375, firstColumnToTop = 0 } = options
      // 1- 确定列数  = 页面的宽度 / 图片的宽度,单例的宽度
      let itemWidth = items[0].width
      // 定义每一列之间的间隙 px
      if (gap === 'auto') {
        gap = (width - itemWidth * column) / (column - 1)
      }
      let _columnHeightArr = [] // 保存每列高度
      let result = []
    
      for (let i = 0, len = items.length; i < len; i++) {
        if (i < column) {
          // 2- 确定第一行
          let top = firstColumnToTop
          let left = (itemWidth + gap) * i + padding
          // 瀑布流列表左右padding
          if (i === 0 || i === len - 1) {
            left = padding
          }
          _columnHeightArr.push(items[i].height - top)
          result.push({
            top,
            left,
            height: items[i].height,
          })
        } else {
          // 其他行
          // 3- 找到数组中最小高度  和 它的索引
          let minHeight = Math.min(..._columnHeightArr)
          let minIndex = _columnHeightArr.findIndex((item) => item === minHeight)
          // 4- 设置下一行的第一个盒子位置
          // top值就是最小列的高度 + gap
          result.push({
            top: _columnHeightArr[minIndex] + gap,
            left: result[minIndex].left,
            height: items[i].height,
          })
    
          // 5- 修改最小列的高度
          // 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 间隙的高度
          _columnHeightArr[minIndex] = _columnHeightArr[minIndex] + items[i].height
        }
      }
    
      return result
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    使用

    vim demo.wxml

        
        <view class="goods_list flex flex-wrap relative" style="width:100%;height:{{height}}">
            <block wx:for="{{list}}" wx:key="index">
              <view class="goods_item" style="position:absolute;top:{{ item.top }}px;left:{{ item.left }}px"
                    bindtap="handleItem" data-item="{{item}}">
                <view class="goods_img">
                  <goodsImage detail="{{ {imgUrl:item.goodsIcon} }}" isAllowCash="{{true}}">
                  goodsImage>
                view>
                <view class="goods_mes">
                  <view class="goods_name g-t-over2">{{item.goodsName}}view>
                  <view class="goods_price">{{item.price}}view>
                view>
              view>
            block>
        view>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    vim demo.js

    // 需要在节点加载到页面后调用
    onReady() {
      const { screenWidth, list } = this.data
      waterFall(this, '.goods_item', { width: screenWidth, gap: 'auto' }).then((arr) => {
        if (!arr.length) return
        let lastNode = arr[arr.length - 1]
        let height = lastNode.top + lastNode.height + 'px'
        this.setData({
          list: list.map((item, index) => ({ ...item, ...arr[index] })),
          height,
        })
      })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    效果

    在这里插入图片描述

    小结

    既然都封装成函数了,为什么不封装成组件调用呢?

    组件调用可以参考这个小程序的瀑布流组件me-waterfall

    我看了这个组件的源码,用到组件间关系来实现瀑布流结构,整个瀑布流组件内部也需要父子组件关系,父组件监听子组件插入元素,获取元素的动态高度,往高度最小列添加新元素。

    但是我引入使用,在组件内使用me-waterfall 组件,组件间关系方法 linked 不生效,官方论坛也找不到原因,便弃用。
    类似这样的结构:

    
    <view>
    	<goodsList />
    view
    
    {list}}" wx:key="index">
    	    <image src="{{item.imgUrl}}" style="width:100%;height:{{item.height}}px" />
    	  me-waterfall-item>
    	me-waterfall>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    于是,自己封装一个方法使用。便有了此文。

    封装成方法也有优点,不需要引入组件,简单引入一下方法调用即可。

  • 相关阅读:
    js预编译习题解题思路
    ROS数据格式转换:LaserScan转MultiEchoLaserScan
    Java中Map详解
    【1day】PHPOK cms SQL注入学习
    Mysql--基础概念
    【逐步剖C++】-第一章-C++类和对象(上)
    WM 报错不含领货点存储类型的存储类型需要部分搁板管理
    def和class的区别
    java计算机毕业设计ssm金华学校社团管理系统(源码+系统+mysql数据库+Lw文档)
    CAS:474922-22-0,DSPE-PEG-MAL,磷脂-聚乙二醇-马来酰亚胺科研试剂供应
  • 原文地址:https://blog.csdn.net/example440982/article/details/128111727