background

After the application of hybrid architecture in the APP, most of the pages in the APP are converted from the original to H5. When dealing with long in-app lists (such as comment lists, etc.), the number of DOM nodes will increase and the performance of the entire page will decrease, hence the application scenario of virtual rolling wheels (Vue based).

reference

A Vue wheel core developers: Akryum/Vue – virtual – scroller this wheel is very considerate, the list items are given height known and consistent, a list item height known but inconsistent, list item height unknown three conditions of solution, the performance of the three conditions are diminishing. The more information you know, the more optimizations you can make, the better performance you get. Based on the ideal situation: when the user renders the list with the same height and is known, the author requires the user to pass in the same height of the list item, and completes the basic component RecycleScroller. Then for the case where the height is known but not guaranteed to be the same, where you don’t know every height at all, you do a further calculation based on the underlying components, with a sense of elegant degradation. The last case, where the real height of the item is calculated in real time at runtime, has the feel of a dynamic language runtime. Correspondingly, the more information you have before you run, the better you can optimize, which is an interesting analogy to compile-time optimization in static languages.

The specific uses of wheels are as follows:

<template>
  <DynamicScroller
    :items="items"
    :min-item-size="54"
    class="scroller"
  >
    <template v-slot="{ item, index, active }">
      <DynamicScrollerItem
        :item="item"
        :active="active"
        :size-dependencies="[
          item.message,
        ]"
        :data-index="index"
      >
        <div class="avatar">
          <img
            :src="item.avatar"
            :key="item.avatar"
            alt="avatar"
            class="image"
          >
        </div>
        <div class="text">{{ item.message }}</div>
      </DynamicScrollerItem>
    </template>
  </DynamicScroller>
</template>

<script>
export default {
  props: {
    items: Array,
  },
}
</script>

<style scoped>
.scroller {
  height: 100%;
}
</style>
Copy the code

As you can see, without knowing the actual height of the item, you must wrap your content with the DynamicScrollerItem component to calculate the actual height of the item.

rendering

The principle of

  1. Support the scroll bar by setting the wrap element height of the list to roughly the sum of all item heights
  2. Render only the viewport and the elements in the buffer above and below the viewport, rendering the minimal DOM
  3. Single item use absolute position +translateY to locate to correct position
  4. When scrolling, switch data and try to reuse DOM according to data item type. If no DOM can be reused, append new DOM

accident

After I developed my own version, in the process of testing the performance, I found that the performance of my component was significantly lower than the official version when the number of elements reached 2000, and the CPU soared to 80% or 90% during scrolling, resulting in a blank screen (dropping frames). The CPU usage of the official version was 20% to 30% of what it was under the same conditions, and it was very smooth (FPS was steady as a dog), so I wanted to find out where my component performance bottlenecks were.

Analysis of the

Chrome Performance

Start with an overview of Performance Kanban in Chrome:

Hot CPU and poor FPS… Select a short period of time when the CPU is full and see what you are doing:

ItemsWithSize is a calculated property that returns an array of items that correspond to the item passed in by the user, plus the actual DOM height of that item. As can be seen from the figure, the calculation of this attribute takes a lot of time, so I visually diff the difference between my version and the official version:

I’m missing two rows with the red icon on them, and each time I take the value directly from this, so how much performance is lost when the number of elements is 2000:

Close to 200 times

optimization

I haven’t figured out why it’s faster to store a variable first than to read it through vue. It doesn’t matter if it’s const or not. The difference between const and var is a matter of another dimension. This is a scary 200 times difference, but in quantitative terms, the difference is about 200ms per calculation. The calculation of this property will loop 2000 times each time, and the value of the variable will be read 5 times from this. So 10,000 reads are 200ms slower, and the calculation speed of one read is 20μs slower. It’s just exposed in this scenario, probably because some of the steps inside the VUE are extra time consuming

Some ideas

In the process of opening (Chao) and sending (XI) the wheel, I encountered an error inside the VUE framework, which stuck for a long time. This is not the first time I have encountered this kind of problem. In the past, WHEN writing other components, I have encountered some errors inside the framework that I do not understand and are difficult to trace. If I am lucky, I can find functions I know from the stack information to debug. But often the errors are so deeply nested that they logically contradict an intuitive web page and are impossible to begin with.

I can’t help but think that front-end development is moving towards framework management, which makes it easy and fast to develop a website with simple business logic. On the one hand, however, frameworks introduce greater complexity, and if the business logic is already complex, it can be difficult to consider the logic within the framework during development. On the other hand, when I wanted to build a simple website, such as my own blog, I started a vue Create and it was a multi-megabyte project… These experiences made me think that there might be some holes to fill in the road to “modular” and “engineered” front-end.