preface

This article contains 4034 words and takes about 11 minutes to read.

Blanket: based on a line of the project as a case, actually from the initial scheme of thinking, to the relative plan selection considerations, to the final practice optimization, is introduced to the readers how to optimize an animation in the small program, so as to improve page performance, to understand the basic operation principle and the small program performance optimization has certain inspiration.

  • Small program animation performance optimization practices
  • Zhihu Column: Front-end Aggressor (Zhihu)
  • Blog address: Damonare’s personal blog

Smiled and cried, is to grow up to understand the helpless

The body of the

background


The appeal of this project is to achieve a 3D effect of the earth and support rotating animation effect, but the small program does not support webGL. We have considered using webview, but this product involves dynamic sharing of copywriting, and the workload of webview development is also quite large. The final solution is to simulate the effect of 3D rotation by playing the picture frame by frame in a small program. After consideration, the final number of images was: 144 PNG images. Each image is compressed at about 30K and the final effect is to switch one image every 125ms. There are two difficulties to consider: one is the preloading of 144 images (image preloading scheme) and the other is the processing of 144 images (image display scheme).

Image preloading


plan

What is image preloading? So-called preload is loaded in advance, the image to the local cache, so that at the time of application used in the image can be directly taken from the cache, the corresponding response speed will be faster, as if we were in a buffet, will take some food to the table in advance, so that we eat is much convenient. We know that on the traditional Web we can often preload images with Image objects, but applets don’t have Image objects, so we need to be flexible. The widget does not provide an Image object, but it does have wx. DownloadFile to use. Of course, we can also preload by hiding many Image components in the page and listening for the Image component to load. Here is sample code for both scenarios:

1. The page hides the image and listens to the onload event to cache the image. Example code is as follows:

<image
   	hidden
    wx:for="{{preLoadImageList}}"
    wx:key="{{index}}"
    data-id="{{item}}"
    src="{{item}}"
    bindload="imageOnload"
    binderror="imageError"
/>
Copy the code

2. Via wechatwx.downloadFileThe API preloads images for caching.

wx.downloadFile({
  // This is an example, not a real resource
  url: '//example.com/img.png', 
  success(res) {
    // Image temporary path
    console.log(res.tempFilePath)
  }
})
Copy the code

The solutions to these two problems are listed above. First, let’s take a look at the pros and cons of image preloading in practice:

Scheme considerations

The two options for image preloading (the image component and wx.downloadFile) are essentially the same, but in some scenarios preloading with wx.downloadFile makes it easier to process our business logic. In addition, in the actual processing process, the author found that in iOS system (normal in Android system), the way of preloading images by hiding the image component was more uncontrollable. Uncontrollable means that when the hidden property of our image component is set to True the image will be recycled from time to time. The author also found the same results when setting the display property of the component to None. This further illustrates that what the applet does to the component’s hidden property is indeed toggle the component’s display property, meaning that the element still exists in the DOM tree. As for the picture will be recycled, the official website also explains:

On iOS, the page of a small program is composed of multiple WKWebViews. When the system is short of memory, some wkWebViews will be recycled. From the past cases we have analyzed, the use of large images and long list images causes WKWebView to be recycled.

(Note: WKWebView is the running environment of the rendering layer in iOS system)

WKWebView does not cache images when the display property of image is None or for a very short time.

The wx. DownloadFileAPI and display are not connected, so it can be cached. In practice, there is no difference between the first method and wx. This drop is because the image needs to be reloaded, and during this process, the image component goes through the process of blank until the image is finished loading, so there will be a significant drop of the frame phenomenon. Wechat looks similar to WKWebView in that it follows a principle of no cache or very short cache time when images are not displayed in the user interface.

After the above analysis, it is basically determined that the reason for picture frame drop is not in the way of image preloading. It is most likely related to the display scheme of the picture. Next, let’s look at the picture display scheme:

Picture shows


plan

Image display schemes are mainly divided into two categories: one is to use Canvas for drawing, the other is to modify DOM, and there are two schemes for DOM modification: one is to switch pictures through image link of single element image component, and the other is to display 144 elements and control the explicit and implicit of 144 elements to switch pictures. Let’s take a look at each of the three options:

1. Canvas drawing is adopted

In the actual operation process of the scheme, it is found that there will be the phenomenon of frame drop, mainly because the premise of drawing is to download the picture first, and then draw it. Even if we download the picture first, there will be obvious flashing problem when we directly use the local path to draw it. In addition, there will be more restrictions on using Canvas drawing. Canvas component is a native component inside the applet. A native component is a component created by the client outside of the WebView rendering process. Native components have many limitations, such as:

  1. The hierarchy is the highest and cannot be setz-indexTo overlay a normal component on top of a native component;
  2. Unable to animate native components with CSS
  3. The native component cannot be defined asposition: fixed;
  4. Cannot be used on parent nodesoverflow: hiddenTo crop the display area of native components;

The first is a common limitation. Imagine that you have a Canvas drawn element on your home page, but some business logic needs to have a popover. In this case, it is very tricky. Of course, small program teams are not unreasonable. They provide a component called cover-View and cover-Image to override the native component. However, the cover-view has another big limitation: only the nested cover-view and cover-image are supported. (For more details on the cover-view limitations, go to the official website. I won’t cover them here.) Therefore, Canvas is not suitable for this project.

2. Display only the current image component and change the image by changing the image link of the component. Example code is as follows:

<image
	catchtouchstart="touchStart"
	catchtouchmove="touchMove"
	catchtouchend="touchEnd"
	src="{{imageUrl}}"
/>
<! - or - >
<view
	catchtouchstart="touchStart"
	catchtouchmove="touchMove"
	catchtouchend="touchEnd"
	style="background-image:url({{imageUrl}})"
/>
Copy the code

3. List all the elements and switch pictures by controlling the explicit and implicit of all pictures. Example code is as follows:

<view
	catchtouchstart="touchStart"
	catchtouchmove="touchMove"
	catchtouchend="touchEnd"
>
    <image
		wx:for="{{imageList}}"
		wx:for-index="idx"
		wx:key="{{idx}}"
		data-id="{{item}}"
		bindload="imageOnload"
		binderror="imageError"
		src="{{item}}"
		style="transform: {{idx === index ? 'translate(0, 0)' : 'translate(-100%, 0)'}};"
    />
</view>
Copy the code

Scheme considerations

1. Show only the current image component and change the image by changing the image link of the component.

From the conclusion of the preloading scheme we can see that scheme 1 is not feasible.

Conclusion: The root cause of the problem lies in the small program’s image recycling and wechat’s cache mechanism, which leads to the uncontrollable phenomenon of frame drop and flash screen when replacing the image link.

2. List all the elements and switch the picture by controlling the explicit and implicit.

In this way, we can only use scheme 2, but scheme 2 needs to render 143 more nodes. In the small program, the performance of setData is proportional to the number of nodes on the page. The more nodes, the lower the efficiency of setData ata time. But there is no more perfect solution… .just have to bite the bullet. Code into the second scheme, the iOS system smoothly not drop frame the splash screen, but android basic card to can’t see, page slide is also problematic, sliding, after a few seconds to have the response, this actually and expectations are consistent, setData itself carries a larger data (page and other logical data), the node number is increased by several times, The impact on performance is exponential. Officials also have an explanation:

  • AndroidThe user will feel stuck when sliding, and the operation feedback delay is serious, becauseJSThe thread has been compiling and executing the render, failing to deliver the user action events to the logical layer and the logical layer failing to deliver the action processing results to the view layer in time;
  • There is a delay in rendering due toWebViewtheJSThreads are always in a busy state, the communication time from the logical layer to the page layer increases, the data message received by the view layer has passed several hundred milliseconds since the time of sending, and the rendering result is not real-time;

The explanation on the website is quite lengthy and in fact I feel it is not easy for beginners to understand. This actually starts with the running environment of the small program. The running environment of applets is divided into the rendering layer and the logic layer, in which WXML templates and WXSS styles work in the rendering layer, and JS scripts work in the logic layer. The communication between them is shown in the following figure (picture from the official website) :

setData
Native
Native

In a traditional Web application, we might modify the DOM directly to change an element, and that’s the end of the road. Small programs don’t work that way, and Native acts as a bridge to forward. This is the root of why we feel caton on Android, because it takes a “detour”.

So we needed to improve the performance of the pages underneath Android, and turning on hardware acceleration was a shortcut. We changed the above code to the following:

<view catchtouchstart="touchStart" catchtouchmove="touchMove" catchtouchend="touchEnd" > <image wx:for="{{imageList}}" wx:for-index="idx" wx:key="{{idx}}" data-id="{{item}}" bindload="imageOnload" binderror="imageError" src="{{item}}" style="transform: {{idx === index ? 'translate3d(0, 0, 0)' : 'translate3d(-100%, 0, 0)'}};" <! --> /> </view>Copy the code

When hardware acceleration is enabled, Android does not lag, and the page can be swiped left and right. But… Apple’s iOS is back on track… Translate3d | translate3D | Translate3D | Translate3D | Translate3D | Translate3D | Translate3D | Translate3D | Translate3D | Translate3D As for the flash screen problem of translate3D animation on iPhone, the root cause of this problem is that WebKit will separate the element with translate3D into a layer for rendering, but it does not have the inner child element. The solution is to cache the child element as well. Render as a separate layer. However, the image component of the applet is actually a multi-layer DOM structure. The imagined structure is as follows:

<div style="display:inline-block; ...">
    <div style="display:inline-block; background-image:url({{url}}); ...">.</div>
</div>
Copy the code

There is no way to control the child elements of the image component in the applet. The temporary solution is to enable GPU acceleration by differentiating between the current user’s operating system, i.e. using Translate3D;

tuning


Custom Components

As of version 1.6.3 of the base library, applets support componentized programming, which also has a unique side for animation components. The website says:

  • Using setData continuously to change the interface can also achieve the effect of animation. This allows you to change the interface arbitrarily, but usually results in significant latency or stalling, or even small program death. At this point, you can improve performance by changing the setData of the page to setData in the custom component.

  • For performance reasons, when using usingComponents, setData content is not directly copied deep, i.e. This.data. field === obj after this.setData({field: obj}). (Deep copy occurs when the value is passed between components.)

A custom Component of an applet is instantiated by a separate constructor, Component, so its setData and the setData of the home page do not affect each other. In this business scenario, the image change and slide logic are encapsulated as a single component and imported from the home page to provide speed of response to these data changes without affecting the home page performance.

WXS responded to the event

WXS is a script language for small programs. Its syntax is basically the same as JS, and it can be used directly nested in WXML. As mentioned in the front end, THE communication between JS and WXML requires the “bridge” Native, so DOM operation cannot be carried out directly. WXS allows you to manipulate the DOM directly.

In fact, in traditional Web development, it is quite common to do an interactive animation with frequent images, and in many cases to manipulate the DOM directly. Applets are different, applets do not have DOM API, now many online saying that applets do not have DOM, actually not accurate, please remember: applets have DOM but do not have DOM API and BOM API. So it can’t use the jQuery library directly.

In WXS, we no longer use setData, which is the API used for interaction between JS and WXML. In WXS, we can directly modify the DOM tree, but WXS response events are only supported by the small program base library 2.4.4. Considering compatibility, this solution is not adopted in this example. For more details on how WXS modifies the DOM, see the WXS Response Events website.

conclusion


To sum up, we solved the problem of flicker by preloading and controlling the visibility of each image, and we solved the problem of animation stuttering by customizing components and enabling GPU acceleration. Wx. DownloadFile is used to preload pictures and list all elements. The purpose of switching pictures is achieved by controlling the explicit and implicit of each picture. This is the final technical scheme of the animation. In the process we have listed many other solutions that can also be used as references and may be better solutions in certain business scenarios. The above scheme focuses more on the ability of the small program, aside from the small program itself, the actual consideration of the picture is not a lot, such as the format of the picture (using webP, APNG format pictures), size (whether there is compressible space), etc..