Example of implementing a virtual list in WeChat Mini Program

Example of implementing a virtual list in WeChat Mini Program

Preface

Most mini programs will have such a requirement. The page has a long list and needs to request background data when scrolling down to the bottom. The data is rendered continuously. When the data list is long, obvious lag will be found and the page will flash white screen.

analyze

  • Requesting background data requires constant setData and continuous data merging, which results in excessive data rendering in the later stage.
  • There are many DOM structures to be rendered, and DOM comparison will be performed every time it is rendered. The related diff algorithms are time-consuming and
  • There are many DOMs, which takes up a lot of memory, causing the page to freeze and display a white screen.

Initial Rendering Method

/*
 * @Descripttion: 
 * @version: 
 * @Author: shijuwang
 * @Date: 2021-07-14 16:40:22
 * @LastEditors: shijuwang
 * @LastEditTime: 2021-07-14 17:10:13
 */
//Page Object
Page({
  data: {
    list: [], // all data},
  //options(Object)
  onLoad: function (options) {
    this.index = 0
    const arr = [
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },




    ]
    this.setData({ list: arr })
  },

  onReachBottom: function () {
    const arr = [
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },

    ]
    let { list } = this.data
    this.setData({
      list: [...list, ...arr]
    })
  },

});


// wxml 
<view class="container">
    <view class="item-list" wx:for="{{list}}">
      <text class="">
          {{item.idx}}
      </text>
    </view>
</view>

Every time the bottom is touched, the data is requested again, the array is merged and the setData is re-set. When the list is responsible, a white screen will appear. The data of setData becomes larger and larger each time, which increases the communication time and renders too many DOM elements, as shown in the following figure:

Initial Optimization

1. Change the one-dimensional array to a two-dimensional array

  • Normally, setData re-renders all data.
  • Paginated requests put relatively less pressure on the server, and paginated incremental rendering also puts less pressure on setData
  • setData searches for the subscript of the data corresponding to the index in the two-dimensional array and uses setData to perform a partial refresh
  • When setData, only the data of the current screen is assigned
// wxml
<view class="container">
  <block wx:for="{{list}}" wx:for-index="pageNuma">
    <view class="item-list" wx:for="{{item}}">
      <text class="">{{item.idx}}</text>
    </view>
  </block>
</view>
// wx.js
Page({
  data: {
    list: [], // all data},
  //options(Object)
  onLoad: function (options) {
    this.index = 0;
    this.currentIndex = 0; // Current page number pageNuma
    const arr = [
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },




    ]
    this.setData({ [`list[${this.currentIndex}]`]: arr })
  },

  onReachBottom: function () {
    this.currentIndex++; // bottom out +1
    const arr = [
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },

    ]
    this.setData({
      [`list[${this.currentIndex}]`]: arr
    })
  },
}); 

In this way, we can see that the entire rendering is rendered in pages based on the screen. If several pageNums are requested, several screens will be rendered, and each list will be rendered in each screen.

Further optimization

2 We can render only one screen of the visible area, or render several screens near the visible area, and use views to occupy other areas without specific rendering, thereby reducing DOM rendering and reducing problems caused by too many nodes.

To do this, you need to split the following steps:

  • Get the current screen height, get the height of each screen when rendering, and calculate the scroll distance
  • Store all the rendered data obtained, store the height of each screen, and calculate which screen the visible area is on through onPageScroll
  • Except for the visible area, the data in other areas is empty and the view is used to occupy the place
    is.currentIndex = 0; // Current page number pageNum
    this.pageHeight = []; //Store each screen height this.allList = []; //Get all the data this.systemHeight = 0; //Screen height this.visualIndex = []; //Store visible area pageNum

When entering the page, mount the relevant data:

 // wxml
 <view wx:for="{{list}}" wx:for-index="pageNum" id="item{{pageNum}}">
    <view class="item-list" wx:for="{{item}}">
      <text class="">{{item.idx}}</text>
    </view>
  </view>
 onLoad: function (options) {
    this.index = 0;
    this.currentIndex = 0; // Current page number pageNum
    this.pageHeight = []; // Each screen height is stored this.allList = []; // All data obtained this.systemHeight = 0; // Screen height const arr = [
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      },
      {
        idx: this.index++
      }
    ]
    this.setData({ [`list[${this.currentIndex}]`]: arr }, () => {

      this.setPageHeight();
    });
    this.getSystemInfo();
  },

Dynamically set the ID name for the view of each screen to facilitate the acquisition and storage of the height of each screen during rendering. Use the loop pageHeight to set the height for subsequent non-visible area placement. Add each page height and the current scroll distance for comparison to obtain the currently rendered pageNum, and then put the currently selected rendered pageNum -+1, the previous screen and the next screen into the array. The current three page data are displayed, and the data of other screens are used for view placement, and the height is the stored height.

   // Scroll distance calculation onPageScroll: throttle(function (e) {
    let pageScrollTop = e[0].scrollTop;  
    let that = this;
    // Scroll to calculate the current page screen let scrollTop = 0;
    let currentIndex = this.currentIndex;

    for (var i = 0; i < this.pageHeight.length; i++) {
      scrollTop = scrollTop + this.pageHeight[i];

      if (scrollTop > pageScrollTop + this.systemHeight - 50) {
        this.currentIndex = i;
        this.visualIndex = [i - 1, i, i + 1];
        that.setData({
          visualIndex: this.visualIndex
        })
        break;
      }
    }
  },200)

Real-time monitoring of scrolling and throttling optimization

const throttle = (fn, interval) => {
  var enterTime = 0; //Trigger time var gapTime = interval || 300; //Interval time, if interval is not passed, the default is 300ms
  return function () {
    var that = this;
    var backTime = new Date(); //The time when the function returns for the first time is triggered if (backTime - enterTime > gapTime) {
      fn.call(that, arguments);
      enterTime = backTime; //Assign the time of the first trigger to save the time of the second trigger}
  };
}

Get the visible area array, and then determine whether the current pageNum is in the visualIndex. If it is, render it. If not, the view takes up space and the height is obtained and stored.

<wxs module='filter'>
 var includesList = function(list,currentIndex){
   if(list){
    return list.indexOf(currentIndex) > -1
   }
 }
 module.exports.includesList = includesList;
</wxs>
<view class="container">
 <view wx:for="{{list}}" wx:for-index="pageNum" id="item{{pageNum}}" wx:key="pageNum">
   <block wx:if="{{filter.includesList(visualIndex,pageNum)}}">
     <view class="item-list" wx:for="{{item}}">
       <text class="">{{item.idx}}</text>
     </view>
   </block>
   <block wx:else>
     <view class="item-visible" style="height:{{pageHeight[pageNum]}}px"></view>
   </block>
 </view>
</view>

If the pageNum of the array in the visible area is not in the current array, the height placeholder is set. The rendering effect is as follows:

This method does the job!

Method 2

Using WeChat Mini Program API
IntersectionObserver

  //View monitor observePage: function (pageNum) {
    const that = this;
    const observerView = wx.createIntersectionObserver(this).relativeToViewport({ top: 0, bottom: 0});
    observerView.observe(`#item${pageNum}`, (res) => {
      console.log(res,'res');
      if (res.intersectionRatio > 0) {
        that.setData({
          visualIndex: [pageNum - 1, pageNum, pageNum + 1]
        })

      }
    })
  }

Use oberserve to monitor whether the current page is in the visible area. If so, put the current pageNum -+1, and put pageNum into the visible area array visualIndex. Use wxs to determine whether the current pageNum exists in the visible area array. If so, render it. If not, use view to occupy the place.

Solution 1: Scrolling calculation >>>Code snippet: (scrolling virtual list)

Solution 2 IntersectionObserver api >>> Code snippet: intersectionObserver

This is the end of this article about the implementation example of the WeChat mini-program virtual list. For more relevant mini-program virtual list content, please search for previous articles on 123WORDPRESS.COM or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • Application examples of WeChat applet virtual list

<<:  How to use port 80 in Tomcat under Linux system

>>:  In-depth explanation of the impact of NULL on indexes in MySQL

Recommend

Detailed explanation of Docker container data volumes

What is Let’s first look at the concept of Docker...

Example of adding multi-language function to Vue background management

Table of contents 1. First, configure the main.js...

A brief introduction to JavaScript arrays

Table of contents Introduction to Arrays Array li...

Nginx operation and maintenance domain name verification method example

When configuring the interface domain name, each ...

MySQL database Shell import_table data import

Table of contents MySQL Shell import_table data i...

How React Hooks Work

Table of contents 1. React Hooks vs. Pure Functio...

Summary of considerations for writing web front-end code

1. It is best to add a sentence like this before t...

How to implement insert if none and update if yes in MySql

summary In some scenarios, there may be such a re...

Detailed explanation of the use of MySQL DML statements

Preface: In the previous article, we mainly intro...

Detailed steps for installing JDK and Tomcat on Linux cloud server (recommended)

Download and install JDK Step 1: First download t...

Implementation of running springboot project with Docker

Introduction: The configuration of Docker running...

How to configure MySQL master-slave replication under Windows

MySQL master-slave replication allows data from o...

A brief analysis of adding listener events when value changes in html input

The effect to be achieved In many cases, we will ...