小程序瀑布流

作者: 一只小九九 | 来源:发表于2018-10-14 23:42 被阅读396次

最近在做一个小程序项目,遇到如题问题,在网上各种搜索解决方案不如人意,什么flex布局、多列布局、cloumn诸如此类都达不到需求。于是决定自己炸馍炸馍。最终有情人终成眷属(什么鬼= =..)

效果如图: timg1.png

首先我们需要考虑几个问题

1 小程序没有类似H5简单粗暴直接操作dom的api。

2 瀑布流和无限加载问题(绑定关系)

解决以上问题之后,就可以一路向西了。

我们先讨论第一个问题,操作dom无非是想直接获取dom信息,width、height等等,然后开始布局,如果获取到这些瀑布流布局问题迎刃而解。

解决思路如下

1 首页是一堆list列表组件,每个组件展示一页数据,因此我们页面获取的数据需要是二维数组, 这么做的好处是当数据量过大时,避免小程序赋值操作this.setData的大小限制1024KB。


timg6.png

2 每一个list开始获取image相关信息,通过小程序image组件bindload事件获取,然后计算元素定位位置top值、left值,使用定位absolute,然后计算list列表最外层view(position;relative)实际高度,完成后页面就由每10条数据为一页、一页一页的展示。这样处理会有一个需要注意的地方,上一页列表产生的高度差需要计算出来传递个下一页列表计算定位问题,我们通过主页面为桥梁传递上一个列表所产生的dValue高度差。


timg5.png

index.wxml:

<!-- 列表 -->
<list wx:for="{{list}}" d-value="{{dValue}}" list-data="{{item}}" bind:heightlog="heightlog"></list>
<!-- loading -->
<view style='height: 200rpx; text-align: center; line-height: 1; padding: 86rpx 0; font-size: 28rpx; color: #999;'>
 <text hidden='{{!isLoading}}'>正在加载...</text>
</view>

其中list为二维数组,item为二维数组子元素[ ]包含10条数据,d-value传递给list组件,告诉当前组件上一个列表产生的高度差,用于当前组件计算元素top值
index.json:

{
  "usingComponents": {
    "list": "./template/list/"
  }
}

index.js:

const app = getApp()

Page({
  data: {
    list: [], // 模拟数据
    dValue: 0, // 列表高度差
    isLoading: false // 阻止无限触发加载阈值
  },
  onLoad: function () {
    this.getlist() // 初始化列表数据
  },
  onReachBottom () { // 触底函数
    this.getlist()
  },
  heightlog (e) {
    this.setData({ dValue: e.detail})
  },
  getlist () {
    if (this.data.isLoading) return
    this.setData({ isLoading: true })
    setTimeout(() => { // 模拟ajax获取数据
      this.setData({ isLoading: false })
      let imgArr = []
      for(var i = 0; i < 10; i++) {
        imgArr.push({
          imgUrl: '/img/demo' + (Math.ceil(Math.random() * 10)) + '.jpg',
          desc: '业精于勤,荒于嬉。行成于思,毁于随。'
        })
      }
      let page = this.data.list.length
      let itemlist = 'list[' + page + ']' // 关键点,二维数组渲染组件,每个组件得到10条数据,避免setData数据量过大问题
      this.setData({
        [itemlist]: imgArr
      })
    }, 1000)
  }
})

完成index页面的数据、以及无限加载之后,重点在与list组件怎么渲染数据了。
话不多说直接上代码:
list.wxml

<!--template/list/list.wxml-->

<image wx:for="{{images}}" data-index='{{index}}' src="{{item.imgUrl}}" hidden bindload="imgOnload"></image>

<view class="img-group" style='height: {{wrapperHeight}}px'>
<view class='img-wrapper' wx:for="{{list}}" wx:key="{{index}}" style="width: calc(50% - {{3*gap/2}}px); top: {{item.top}}px; left: {{item.left}}px">
  <image mode='widthFix' src="{{item.imgUrl}}" style="height:{{item.showHeight}}px"></image>
  <view class='desc' wx:if="{{item.desc != ''}}">{{item.desc}}</view>
</view>
</view>

list.json:

{
  "component": true,
  "usingComponents": {}
}

list.wxss:

/* template/list/list.wxss */
.img-group {
  position: relative;
}
.img-wrapper {
  position: absolute;
  font-size: 0;
}
.img-wrapper image {
  width: 100%;
  height: auto;
  border-radius: 10rpx;
  margin-bottom: 5px;
}
.img-wrapper .desc {
  font-size: 14px;
  color: #555555;
  line-height: 1;
  height: 15px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

list.js:

// index/template/list.js
const app = getApp() // 获取app信息
Component({
  // 组件的属性列表
  properties: {
    listData: {
      type: Object,
      value: null,
      observer: function (newVal, oldVal, changedPath) {
        // console.log(newVal)
        this.setData({
          images: newVal
        })
      }
    },
    dValue: {
      type: Number,
      value: 0,
      observer: function (newVal, oldVal, changedPath) {
        // console.log(newVal)
        if (this.data.HValue !=0) return // 避免重新赋值导致布局变化
        this.setData({
          HValue: newVal
        })
      }
    }
  },

  // 组件生命周期函数,在组件布局完成后执行,此时可以获取节点信息
  ready () {
    this.setData({ windowWidth: app.systemInfo.windowWidth }) // 设置屏幕宽度
  },

  // 组件的初始数据
  data: {
    windowWidth: 0, // 屏幕宽度
    HValue: 0, // 上一个列表高度差
    gap: 15, // 元素之间间隔
    images: [], // 获取元素信息
    initlist: [], // 判断长度作用,避免获取所有数据没有完成之前开始渲染
    list: [], // 真正渲染的处理过的数据
    wrapperHeight: 0 // 需要更新的最外层盒子高度
  },

  // 组件的方法列表
  methods: {
    imgOnload (e) {
      let options = e
      let imgItem = {}
      let index = options.target.dataset.index // 保证传递过来的数据索引正确
      imgItem = this.data.images[index] // 获取item信息
      imgItem.width = options.detail.width // 获取图片宽度
      imgItem.height = options.detail.height // 获取图片高度
      let item = 'initlist[' + index + ']'
      this.setData({
        [item]: imgItem
      })
      // 判断长度相等再赋值给真正渲染的数据,避免数据不全就开始渲染
      let nums = 0
      for (var i = 0; i < this.data.initlist.length; i++) {
        if (this.data.initlist[i] == undefined) {
          nums++
        }
      }
      if (this.data.initlist.length == this.data.images.length && nums == 0 && this.data.initlist.length > 0) {
        this.setData({ list: this.data.initlist })
        this.calcOffset()
      }
    },
    calcOffset () {
      let list = JSON.parse(JSON.stringify(this.data.list))
      let dValue = this.data.HValue // 上个列表产生的高度差
      let arr = [0, 0] // 列高度数组, 此案列采用2列
      let gap = this.data.gap // item间隙
      let otherH = 20 // 其他内容高度 描述信息等等
      let itemWidth = (this.data.windowWidth - 3 * gap) / 2 // 元素实际展示宽度
      if (dValue > 0) {
        arr[1] = -dValue
      } else {
        arr[0] = dValue
      }
      for (var i = 0; i < list.length; i++) {
        list[i].showHeight = itemWidth * list[i].height / list[i].width // 元素实际展示高度
        let minHeight = arr[0] // 找到最小高度
        let index = 0
        for (var j = 0; j < arr.length; j++) {
          if (minHeight > arr[j]) {
            minHeight = arr[j]
            index = j
          }
        }
        list[i].top = arr[index] // 设置元素top值
        list[i].left = index * (itemWidth + gap) + gap // 设置元素left值
        list[i].offsetHeight = itemWidth * list[i].height / list[i].width + gap + otherH // 元素总高度
        if (list[i].desc == '') {
          list[i].offsetHeight -= otherH // 当没有简介内容时去掉高度
        }
        arr[index] = arr[index] + list[i].offsetHeight // 更新列高度数组
      }
      let iMax = Math.max(...arr) // 计算列最大值 ,更新最外层盒子高度
      this.setData({
        list: list,
        wrapperHeight: iMax
      })
      let newDvalue = parseInt(arr[0] - arr[1]) // 产生的新的列表高度差
      this.triggerEvent('heightlog', newDvalue) // 子组件通信,告知新的列表高度差,用于新数据计算高度差
    }
  }
})

以上就是解决小程序瀑布流的方案了,如果小伙伴有更好的方案或者疑问,请联系我。
转载请注明出处,谢谢!
github:https://github.com/lindongxu2017/waterfall

相关文章

  • 小程序瀑布流

    最近在做一个小程序项目,遇到如题问题,在网上各种搜索解决方案不如人意,什么flex布局、多列布局、cloumn诸如...

  • 小程序瀑布流

    1.带有图片widthFix自适应的盒子,瀑布流布局ps: 注意点有因页面加载渲染问题,需要在this.setDa...

  • 小程序瀑布流

    使用 css 实现小程序中的 瀑布流 wxml文件内容 wxss文件内容 重点在于:column-count: 2...

  • 小程序瀑布流组件:支持翻页与图片懒加载

    电商小程序中,用到瀑布流的地方非常多,每次都写一个瀑布流,重复一次逻辑,作为程序员,肯定是非常不愿意的。瀑布流的形...

  • 小程序-瀑布流插件

    BrickLayout 晓瀑布 来源:https://www.ifanr.com/minapp/1085422 g...

  • 小程序瀑布流心得

    遇到的问题 小程序提供了获取节点的api,最开始的实现思路为左右两个盒子分别渲染数组 ,从左边先开始添加一条数据随...

  • 小程序-不用插件实现瀑布流

    前段时间正好需要在小程序开发遇到了需要瀑布流列表,做好了记录一下 网页的瀑布流有很多插件可以用,小程序的也可以用插...

  • 微信小程序实现瀑布流布局

    小程序实现瀑布流 近期在做APP转成小程序的相关开发,需将APP里面的部分页面抽离出来,做成小程序。其中有个页面是...

  • 微信小程序实现瀑布流布局(一)

    京东微信小程序瀑布流的效果: 分析结构 瀑布流可以看成是由一个个的小卡片左右分布组成 1.图片 2.标签(有些有)...

  • 微信小程序瀑布流列表

    小程序的瀑布流列表,如果是两列,把屏幕分为2列,左边一列,右边一列。 在wxss里面,定义各个view的位置。

网友评论

    本文标题:小程序瀑布流

    本文链接:https://www.haomeiwen.com/subject/zwcxzftx.html