实现类似小红书首页照片墙瀑布流的效果

1.实现左右两侧布局
2.获取图片高度动态插入高度小的一列
3.长列表滚动优化

1.0版本

页面左右两侧布局

   <scroll-view
      scroll-y='true'
      class='category-wrapper'
      bindscrolltolower="scrolltolower"
      threshold="100"
    >
      <view class="fall">
        <view class="fall-column" wx:for="{{picList}}" wx:for-item="column" wx:key="index">
          <view class="fall-column-item" wx:for="{{column}}" wx:key="index" wx:for-item="item">
            <image
              lazy-load
              class="fall-column-item-img"
              src="{{item.imgUrl}}"
              data-url="{{item.imgUrl}}"
              data-id="{{item.id}}"
              mode="widthFix"
              bindtap="preview"
              >
            </image>
            <view bind:longpress="toCopy" data-text="{{item.description}}"  wx:if="{{item.description}}">{{item.description}}</view>
          </view>
        </view>
      </view>
    </scroll-view>

2.通过wx.getimageinfo获取图片信息,图片高度
用一个长度为2的数组heightArr记录已经加载的图片的高度,比较第1项和第2项的大小,然后把新的图片的高度累加到较小的一项上。

import api from '../../api/index'
Page({
  data: {
    list: [],
    heightArr: []
  },
  async onLoad () {
    let {results} = await api.fetchImages()
    let col = 2
    for (let i in results) {
      results[i].cover = results[i].imageUrl
      // 获取图片信息
      let info = await this.loadImage(results[i].cover)
      results[i].height = 165 / info.width * info.height
      if (i < col) {
        this.data.list.push([results[i]])
        this.data.heightArr.push(results[i].height)
      } else {
        let minHeight = Math.min.apply(null, this.data.heightArr)
        let minHeightIndex = this.data.heightArr.indexOf(minHeight)
        this.data.list[minHeightIndex].push(results[i])
        this.data.heightArr[minHeightIndex] += results[i].height
      }
    }
    this.setData({
      list: this.data.list
    })
  },
  loadImage (cover) {
    return new Promise(resolve => {
      wx.getImageInfo({
        src: cover,
        success: (res) => {
          resolve(res)
        }
      })
    })
  }
})

通过添加伪元素过渡图片加载显示的效果

.fall-column-item::after {
  content: '加载中';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: inline-block;
  color: #666;
}

wx.getImageinfo接口会先把图片下载到本地,一次下载量上限是10,由于多了本地路径的时间,所以图片加载的速度较慢。

2.0

渲染层优化,由于数据是分页加载的,在渲染层上通过一个隐藏的hidden节点,image标签的bindload事件来获取图片的高度,速度有明显提升。
优点:载入速度加快了,不必要的图片下载请求减少了
缺点:渲染了不必要的节点

长列表的优化,滚动加载数据,节点会不断增加,在1.0的版本上,数据是一维数组,每次更新,随着数据量的加大,数组会越来越长。可以改成二维数组,每次仅更新数组的某一项。但是节点的数量并没有减少,因此,参考微信官方recycle-view的思想,https://developers.weixin.qq....,进行长列表的优化,在滚动过程中,只渲染固定页数的节点,将其余的节点,留一个空的节点占位符,为防止白屏和页面抖动的问题,给空节点一个页面内容数据的高度。
核心思想

1.由于是左右两列布局,所以数组的是[[column1], [column2]]的结构
2.由于要分页加载,所以要获取每一页数据渲染在页面的内容的高度,通过计算滚动的距离scrollTop + windowHeight(区域的高度)和记录每一页数据的高度的数组pageHeightArr来比较,当前应该渲染的是第几页的数据,这时数据结构变成三维数组[[[page1], [page2]], [[page1], [page2]]]

注意:在初始化后滚动时获取内容区高度时,在滚动过程中可能会出现获取内容区高度时0的情况,所以,后期改成在图片加载成功后,获取内容区的高度。

渲染层代码

 <scroll-view
      id="contain"
      scroll-y='true'
      class='category-wrapper'
      bindscrolltolower="scrolltolower"
      bindscroll="scroll"
      threshold="100"
    >
      <view class="wrap">
        <view wx:for="{{finList}}"  wx:for-index="index1" wx:for-item="column">
          <block wx:if="{{column.length > 0}}">
            <view id="wrap{{index1}}_{{index}}" wx:for="{{column}}" wx:key="index" wx:for-item="item">
              <view wx:for="{{item}}" wx:key="index" wx:for-item="el">
                <image
                  lazy-load
                  src="{{el.imgUrl}}"
                  data-url="{{el.imgUrl}}"
                  data-id="{{el.id}}"
                  mode="widthFix"
                  bindtap="preview"
                >
                </image>
                <view bind:longpress="toCopy" data-text="{{el.description}}"  wx:if="{{el.description}}">{{el.description}}</view>
              </view>
              <view class="placeHolder" v-else style="height: {{item.height}}px"></view>
            </view>
          </block>
        </view>
      </view>
    </scroll-view>
    <view class="hidden">
      <block wx:for="{{hiddenList}}" wx:key="index">
        <image src="{{item.imgUrl}}" data-index="{{index}}" bindload="imgLoad" binderror="imgError"></image>
      </block>
    </view>

逻辑层代码
在一页的数据加载完成后,用wholeList记录全量数据,finList记录渲染的数据,heightArr记录每一页完成后,下一页的第一项数据应该插入高度较小的那一列。

   async allLoad () {
      let fin = true
      for (let i = 0; i < this.data.hiddenList.length; i++) {
        if (!this.data.hiddenList[i].load) {
          fin = false
          break
        }
      }
      if (fin) {
        let arr = [[], []]
        let height = this.data.heightArr
        for (let i = 0; i < this.data.hiddenList.length; i++ ) {
          if (i < 2 && !height[i]) {
            arr[i].push(this.data.hiddenList[i])
            height[i] = this.data.hiddenList[i].height
          } else {
            let min = height[0] <= height[1] ? 0 : 1
            height[min] += this.data.hiddenList[i].height
            arr[min].push(this.data.hiddenList[i])
          }
        }
        for (let i = 0; i < arr.length; i++) {
          this.data.finList[i][this.data.index] = arr[i]
        }
        this.getwrapHeight()
        this.setData({
          finList: this.data.finList,
          wholeList: JSON.parse(JSON.stringify(this.data.finList)),
          heightArr: height
        })
      }
    },
    

获取每一次加载后,左右两列内容的高度

    async getwrapHeight () {
      setTimeout(async () => {
        const contain1 = await createSelectorQuery.call(this, `#wrap0_${this.data.index}`);
        const contain2 = await createSelectorQuery.call(this, `#wrap1_${this.data.index}`);
        // console.log('左侧' + contain1.height)
        // console.log('右侧' + contain2.height)
        this.setData({
          ['pageHeightArr[' + this.data.index + ']']: Math.max(contain1.height, contain2.height)
        })
      }, 500)
    },

滚动过程中始终只渲染最大固定2页的数据,(例如在加载第4页时,应该将第1页和第二页的节点数换成只有固定高的空节点)

    scroll (e) {
      this.$throttle(() => {
        let height = 0
        for (let i = 0; i <= this.data.pageHeightArr.length; i++) {
          height += this.data.pageHeightArr[i]
          if (height > e.detail.scrollTop + this.data.windowHeight) {
            this.setData({
              curIndex: i
            })
            break
          }
        }
        let tempList = new Array(this.data.index + 1).fill(0); //
        tempList.forEach((item, i) => {
          if (i >= this.data.curIndex - 1 && i <= this.data.curIndex + 1) {
            for (let col = 0; col < 2; col++) {
              this.data.finList[col][i] = this.data.wholeList[col][i]
            }
          } else {
            this.data.finList[0][i] =  this.data.finList[1][i] = {height: this.data.pageHeightArr[i]}
          }
        })
        this.setData({
          finList: this.data.finList
        })
      })
    },
效果如图


可以看到,wrap1_0和wrap_1里由占位节点给了内容高度撑开。

总结

1.在1.0的版本上获取图片高度的方案上,选择wx.getimageinfo的方式并不是一个好方法,由于图片多了本地路径的下载,所以加载速度较慢,有明显的加载等待时间。所以在2.0里通过image的bindload来获取高度,需要注意,也需要有图片加载失败的binderror的处理。
2.瀑布流抽象出来其实也是长列表,在长列表的优化上,可以将一维数组改成二维数组的方式,来减少逻辑层和渲染层的通信。在无限滚动加载中,可以通过滚动距离的计算,只渲染固定几页的数据,减少在加载过程中不断增加渲染节点,不断通信带来的性能问题。
3.如果是固定item的长列表,可直接使用微信官方的recycle-view插件来做性能优化。不定高瀑布流等长列表,核心思想也是相同的。


flymetotheMoon
1 声望0 粉丝

« 上一篇
uni-app-01
下一篇 »
设计模式笔记