likes
comments
collection
share

小程序技巧系列——两栏瀑布流实现

作者站长头像
站长
· 阅读数 37

前言:在移动端两栏的瀑布流交互方式还是很常见的,大多用在图文的展示上,在这里记录下自己的实现方式,供有需要的人参数

什么是瀑布流布局

视觉表现就是一种参差不齐的多栏布局,随着页面的滚动,随之加载数据。其实本质就是容器内有多个高度不一致的元素展示,既多行等宽元素的一种顺序排列。

布局原理

本片文章只实现移动端比较常见的两栏瀑布流,并且是小程序生产环境可用的,所以就不再赘述一些其他方式的实现。

  • 页面分成左右两个容器,各对应一组数据。
  • 将每一列当做一个容器,在数据侧先将数据分为左右两个数组。每次插入时,计算下左右的高度,依次插入到高度较低的那一个容器。

实际效果

小程序技巧系列——两栏瀑布流实现

实现

新建waterfall自定义组件

布局

<view class="waterfall-wrap">
    <view class="waterfall-column" style="width: 340rpx">
        <view class="left-container" id="left">
            <block wx:for="{{leftList}}" wx:key="{{item}}">
                <view class="item" style="height: {{item}}rpx"></view>
            </block>
        </view>
    </view>
    <view class="waterfall-column" style="width: 340rpx">
        <view class="right-container" id="right">
            <block wx:for="{{rightList}}" wx:key="{{item}}">
                <view class="item" style="height: {{item}}rpx"></view>
            </block>
        </view>
    </view>
</view>
.waterfall-wrap {
    display: flex;
    width: 100%;
    box-sizing: border-box;
    justify-content: space-between;
}
.item {
    width: 100%;
    border: 1px solid rosybrown;
    border-radius: 16rpx;
    margin-bottom: 20rpx;
}

渲染逻辑

使用小程序提供的boundingClientRect获取布局节点的位置信息,这样就可以获取到容器的高度,来做插入判断。 具体实现,看代码:

Component({
    data: {
        leftList: [],
        rightList: []
    },
    methods: {
        oneByOneRender(data = [], i, success) {
            if (data.length > i) {
                this.getBoundingClientRect((res) => {
                    const rects = res[0];
                    if (rects && rects.length) {
                        const leftH = rects[0].height;
                        const rightH = rects[1].height;

                        if (leftH <= rightH + 10) {
                            this.data.leftList.push(data[i]);
                        } else {
                            this.data.rightList.push(data[i]);
                        }
                        this.setData({
                            leftList: this.data.leftList,
                            rightList: this.data.rightList
                        }, () => {
                            this.oneByOneRender(data, ++i, success);
                        })
                    }
                })
            } else {
                success && success();
            }
        },
        run(data, success, isLast) {
            if(this.columnNodes) {
                this.render(data, success, isLast);
            } else {
                this.selectDom(() => {
                    this.render(data, success, isLast);
                })
            }
        },
        getBoundingClientRect(cb){
            this.columnNodes.boundingClientRect().exec(cb)
        },
        render(data = [], success, isLast) {
            if(isLast) {
               return this.oneByOneRender(data, 0, success)
            }
            this.columnNodes.boundingClientRect().exec((res) => {
                const rects = res[0];
                if (rects && rects.length) {
                  let container = '';
                  if (rects[0].height <= rects[1].height) {
                    container = 'leftList';
                  } else {
                    container = 'rightList';
                  }
                  data.forEach(item => {
                    this.data[container].push(item);
                    if (container === 'leftList') {
                        container = 'rightList'
                    } else {
                        container = 'leftList'
                    }
                  })
                }
                this.setData({
                    leftList: this.data.leftList,
                    rightList: this.data.rightList
                }, () => {
                })
            })
        },
        selectDom(cb) {
            const query = this.createSelectorQuery();
            this.columnNodes = query.selectAll('#left, #right');
            cb && cb();
        }
    },
});

性能优化

由于小程序的双线程架构,频繁的setData会对性能有较大的损害,因此我们要尽可能的减少setData,因此对于一个瀑布流来说,我们正常滚动过程中,对一次加载的数据,先判断当前左右容器的高度,然后将数据遍历,分成2份,最后直接一次setData。 只有当是最后一页数据时,保证我们瀑布流不会出现一边很高,一边很低。这个时候我们做到每插入一个元素都去计算左右容器的高度,保证正确的插入顺序,样式上做到符合预期。 代码已在上面体现。

调用

调用这里,我们不使用数据传递,而是直接调用自定义组件内的方法,在使用的页面内:

 // 模拟接口数据
    setTimeout(() => {
      this.waterfallRef = this.selectComponent('#waterfallFlow');
      this.waterfallRef.run(this.data.dataList, () => {
      });
    }, 1000)
 // 加载更多   
 onReachBottom() {
    this.waterfallRef.run(this.data.dataList, () => {
    }, true);
  },

这里是使用页面的滚动到底部的钩子函数,同理也可以使用在scroll-view组件内。

总结

本文的重点其实性能优化这点,由于小程序双线程的架构,因此数据-》渲染这块,整体效率较低。为了交互更友好,体验更好。因此我这里想的是,正常的加载过程中,只判断当前容器的高度,然后直接将数据分为两份,然后对应加载。只有当是最后一页时,才没插入一个计算一个,保证最后左右两侧不会出现一边很高,一边很低的现象。

转载自:https://juejin.cn/post/7066778804827619364
评论
请登录