“This is the fourth day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Preface: The waterfall flow interaction mode in the mobile terminal is very common, mostly used in the display of pictures and texts, here to record their own way of implementation, for people who need parameters

What is waterfall flow layout

Visual representation is a jagged multi-column layout that loads data as the page scrolls. In fact, the essence of the container is the display of multiple highly inconsistent elements, which is a sequence of multiple rows of equal width elements.

The layout principle

This article only implements the two column waterfall flow that is common on the mobile end, and is available in the production environment of the small program, so there is no need to describe some other methods of implementation.

  • The page is divided into left and right containers, each corresponding to a set of data.
  • Treat each column as a container and divide the data into left and right arrays on the data side. At each insertion, calculate the left and right height, and then insert the container with the lower height in turn.

The actual effect

implementation

New Waterfall custom components

layout

<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>
Copy the code
.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;
}
Copy the code

Rendering logic

Use boundingClientRect provided by the applet to get the location information of the layout nodes, so that you can get the height of the container for insertion judgment. Concrete implementation, see the code:

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(); }}});Copy the code

Performance optimization

Due to the dual-threaded architecture of the small program, frequent setData will have great damage to performance, so we should reduce setData as much as possible. Therefore, for a waterfall flow, we should judge the height of the current left and right containers of the data loaded once in the normal scrolling process, and then traverse the data into two parts. One last direct setData. Only when it’s the last page of data, make sure we don’t have a waterfall flow that’s high on one side and low on the other. At this time, we calculated the height of the left and right containers for each element to ensure the correct insertion sequence and meet the expected style. The code is shown above.

call

Here, instead of using data passing, we call a method directly within the custom component, within the page used:

 // Simulate interface data
    setTimeout(() = > {
      this.waterfallRef = this.selectComponent('#waterfallFlow');
      this.waterfallRef.run(this.data.dataList, () = >{}); },1000)
 // Load more
 onReachBottom() {
    this.waterfallRef.run(this.data.dataList, () = >{},true);
  },
Copy the code

Here is the hook function that uses the scroll to the bottom of the page, which can also be used in the scrollview component.

conclusion

The focus of this article is actually performance optimization, due to the small program dual-threaded architecture, so the data – rendering this, the overall efficiency is low. To make the interaction more friendly and the experience better. So what I’m thinking here is that in the normal loading process, I only judge the height of the current container, and then directly divide the data into two parts, and then load them accordingly. It’s only on the last page that you don’t insert one and you don’t compute one, so you don’t end up with one side high and one side low.