The reason for writing this article: I recently encountered performance problems caused by excessive page image elements in my company’s small program project. The performance detection panel provided by the miniprogram was analyzed to determine that the image elements took up too much memory.

Since I was mainly engaged in desktop application development and native APP development, I did not take into account the memory occupation of mobile images. This time since encountered, also take advantage of this opportunity to learn about its optimization skills.

What caused the performance problem

Simply put: too many DOM nodes && too many image nodes

Too many DOM nodes cause more memory usage. According to the current micro channel small program limit, memory occupancy of 500 MB or more will be stuck, or even flash back. If the nodes in the list do not have image labels, the memory footprint will not be significant, but too many DOM nodes will increase the page rendering time. But when the list nodes contain images, the memory footprint climbs quickly.

How to solve these two problems?

For the above two points, we have corresponding optimization ideas respectively

1. Too many DOM nodes.

For an infinitely loaded page, each element in the list has a large number of child nodes. As the number of lists increases, the total number of nodes on the page explodes. Take xiaohongshu’s small program as an example:

As you can see in the image above, this page is a list of many cards. Assuming that a card has 30 DOM child nodes, the page will have 30 * 1000 = 30,000 DOM nodes when the list element is loaded to 1000. Small programs are obviously unbearable (note: the recommended total number of nodes in wechat small programs does not exceed: 1000)

So what can we do to reduce the number of nodes?

The idea is simple: we can only load the real content on the user’s current screen and the upper and lower screens, and use blank nodes to hold the space where other users can’t see it temporarily. After this processing, the actual number of cards with content is not more than 5, and the total number of nodes on the page is: 5 * 30 + 995 = 1145. This is a huge improvement over the previous node count.

Let’s look at the implementation of the code

Before writing the code, let’s sort out the data structures we need.

First of all, this is a List page, we need a List to store the data displayed on the page: showCards. ShowCards will store only 5 pieces of real data, and the rest of the data display will be filled with empty objects.

We also need a List that holds all the real data so that when the user swipes the page, we can get the real data for the card we want to display in real time: totalCards

Page({
  showCards: [].totalCards: []})Copy the code

Next, let’s write the page layout:

<view wx:for="{{showCards}}"
        wx:key="{{index}}">

    <self-define-component data-card-data="{{item}}">
    </self-define-component>
    
</view>
Copy the code

This is what a simple code framework looks like (leaving out a lot of code details that don’t interfere with understanding)

Let’s start by implementing the code logic without optimizing the DOM node. When the page slides to the bottom, push the new card to showCards and update the page with setData. This implements a simple drop-down list page with infinite loading.

async onReachBottom() {
	const newCards = await fetchNewCards();
  this.data.showCards.push(newCards);
  this.setData({
    showCards: this.data.showCards
  })
},
Copy the code

Next we implement the code logic to optimize the DOM node. When the user slides the page (onScroll event), we will judge the position of each card on the current page. If the card is in the upper and lower screens visible to the user, the real data will be displayed; otherwise, it will be replaced with a blank placeholder node with the same width and height as the original card.

In the Page onPageScroll callback, we call the callback function (note that the callback must be throttled, otherwise the Page will flash frequently). Let’s look at the main logic of the reclaim page node function:

In the callback, we first get the location information for each card through the API provided by the applet to get the location of the page element: createSelectorQuery().boundingClientRect.

Next, we use the location information to determine whether to display the real data of card. For cards that don’t show real data, we need to save their height information so that we can fill the page with height information when rendering it. We also give the empty card a type attribute to help us determine the card type when rendering in WXML.

async onScrollCallback() { try { const rectList = await this.calcCardsHeight(); this.recycleCard(rectList); } catch (e) { console.error(e); }}calcFeedHeight() {
    return new Promise((resolve, reject) => {
      this.createSelectorQuery()
        .selectAll(`.card`)
        .boundingClientRect(rectList => {
          resolve(rectList);
        })
        .exec()
    })
  },

  recycleCard(rectList) {
    const newShowCards = [];
    for (let i = 0; i < this.data.showCards.length; i++) {
      const rect = rectList[i];
      if (rect && Math.abs(rectList[i].top - 0) > pageHeight * 2) {
        newShowCards.push({
          type: 'empty-card',
          height: rectList[i].bottom - rectList[i].top
        });
      } else {
      	const feed = totalCards[i];
        newShowCards.push(feed);
      }
    }
    this.setData({
      showCards: newShowCards
    });
  }
Copy the code

Next, we will modify the WXML layout file accordingly:

 <view wx:for="{{showCards}}"
        wx:key="{{index}}">

    <view wx:if="{{item.type === 'empty-card'}}"
          class="card empty-card"
          style="height: {{item.height}}px">
    </view>

    <self-define-component  wx:if="{{item.type ! == 'empty-card'}}"
    			data-card-data="{{item}}"
    			class="card read-card">
    </self-define-component>
    
  </view>
Copy the code

This solves the problem of having too many DOM nodes. (Although users will still see some blank space when they swipe up and down quickly, in most cases, users will not scroll up and down very fast, but read content and slowly slide).

2. Too many image nodes

By optimizing in the previous step, we have significantly reduced the number of images loaded on the page. But in some cases, there is not just one image for each card in our list, and sometimes our image component is a swiper. We assume that each swiper displays an average of 10 images, so if we show 5 cards, there will be 50 nodes. For some low-end Android devices, the overhead can still cause a lag.

So what’s a good optimization? For the previous problem, our optimization idea is to simplify the display of nodes in a place where users can’t see them.

Similarly, with swiper, all the user can see is the current image and the left and right images visible by swiping. The rest of the images can be simplified. As you can see from the figure below, there are only three images that need to be loaded immediately (the red box represents the viewable area of the Swiper component).

We use a variable to record the coordinates of the current swiper display image: curIndex, and then we modify the WXML layout file. The code logic is very simple, that is, by judging the distance between the index of the current Image node and the index of the swiper display node, it will not be displayed if the distance is greater than 2.

After this process, each swiper component will have at most three nodes occupying real memory.

      <swiper-item wx:for="{{images}}"
                   wx:key="{{index}}">

        <view >
          <image class="img"
                 mode="widthFix"
                 src="{{index - curIndex < 2 && index - curIndex > -2 ? item.url : ''}}">
          </image>
        </view>
      </swiper-item>
Copy the code

The last

These are some of the tips I used in this performance optimization, hoping to help you 🙂

If you are interested in my article, here are some of my data visualization, D3.js articles, welcome to fork && Star:

Github.com/ssthouse/ss…

Welcome to follow my official account: