Virtual list

As we all know, DOM number is one of the most direct reasons affecting site performance, how to effectively control the number of DOM, improve page performance is one of the key means to improve user stickiness, product conversion rate, this article will explain how to complete a virtual list like the destruction of the implementation

The body of the

Let’s first look at the difference in rendering time between a normal long list and a virtual list

Normal page

Let’s start by simulating a page:

  <div id="content">
    <div class="item">
       <div class="item-animation"></div> * 10
    </div> * 2000
  </div>
Copy the code
  .content {
    height: 100%;
  }
  .item-animation {
    transform: translate(0);
    transtion: transfrom .5s;
  }
  .item-animation--active {
    transform: translate(10px);
  }
Copy the code
  const item = document.getElementsByClassName('item') [0]
  item.onclick = function() {
    Array.from(item.children).forEach(div= > {
      div.classList.add('item-animation--active')})}Copy the code

If you click on an item, the item-animation will trigger an animation that moves to the right. If you click on an item, the animation will trigger an animation that moves to the right. If you click on an item, the animation will trigger an animation that moves to the right.

We can find that the execution time of the Task executing javascipt click is 51ms at present, which seriously exceeds our normal refresh fluency standard of 16.7ms once, which means that this behavior drops about 2 frames of the page, which will greatly reduce user retention. From the figure below, we can see that it took 7.13ms to generate the renderLayerTree at the end of rendering. It was because of the large volume of elements in the page that it took too long to update the rendering layer at the last step of rendering.

Virtual list

Using the same example above, let’s take a look at how long it takes to animate the child elements triggered by clicking the first item. From the figure below, we can see that for the same animation and the same number of child elements, the Task execution time is 1.21ms, which is 50 times shorter than in the above example 😱.

implementation

precondition

To implement this requirement, we need to know the following points:

  1. The DOMOM obtained by javascript is not the renderObject on the renderTree. Just as we can obtain the display: None node from javascript, we obtain the DOM from the DOMTree
  2. DOMTree updates are real-time
  3. Tasks are divided into MacroTask and MicroTask. After each MacroTask is executed, the rendering process will perform a rendering operation

For more details, read your misinterpretation of nextTick

Isometric virtual list

In the same example above, let’s set up the HTML

  <div class="content">
    <div class="virtual-content"></div>
    <div class="real-content"></div>
  </div>
Copy the code
  html.body.#app {
    height: 100%;
    width: 100%;
  }
  .content {
    position: relative;
    height: 100%;
    overflow-y: auto;
  }
  .real-content {
    position: absolute;
    width: 100%;
    height: 100%;
    left: 0;
    top: 0;
  }
Copy the code

Content: the container that holds the real content, with a height of 100% that inherits the parent virtual-content: the height that supports the content real-content: the container that holds the information that needs to be displayed in the visual window

By default, all items are equal in height, so let’s look at the idea:

  1. The first thing we need to know is what the split height is when all the nodes are loaded in the document, we need to load all the nodes in the Promise microtask into the container virtual-Content, At this time, the DOM corresponding to the container virtual-Content will update its attribute value (such as height) on DOMTree. At this time, we can obtain the height of virtual-Content through JS. Viral-content is used to spread the whole height, so as to achieve the function of simulating scrolling. Assign the height to the virtual-Content after the height is obtained (to ensure that the virtual-Content still extends the height after all child elements are removed later)

  2. Then we remember the visual height of the current screen and the height of the current single item (including margin, border, padding, etc.), so we can get the number of visual items that can be loaded under the current screen size:

    • size = clientHeight / itemHeight

All data is then cached using the collection variable childrenSet (stored for later data presentation), and the virtual-Content content is then cleared. In this case, our steps 1 and 2 above make use of the third element of the precondition to complete the precondition

  1. Define start(childrenSet start) and end (childrenSet end). The definition of end is simple:

    • End = start(position where data starts to be captured) + size(number of items visible)
  2. Listen to the content scroll, constantly refresh the start value, which is the scrollTop of the content divided by the itemHeight (because we have to simulate the process of the item itself drawing out of the viewable area)

    • start = Math.floor(scrollTop / itemHeight)
  3. Since real-content is an absolutely positioned layout, it will map out the viewable area as the content scrolls, so we need to use transform: TranslateY redraws it back to the visible area, but in order to simulate the real sliding scene (since the item itself has a height, the real-content does not need to be pulled back to the visible area when the item is partially drawn), we need to set the corresponding translateY value by calculation. So the start value that we talked about in the last step is crucial, because the start value is the lower limit of the scrollTop divided by the itemHeight, so the remainder is actually the itemHeight, and this part of the value is used to simulate the process of crossing out the item, We don’t need to do any computation. Finally, we just need to pull the real-content back to Start * itemHeight to complete the idea of constant height virtual scrolling

Non-isometric virtual list

The difference between non-contour height and contour height is that the height of items cannot be determined, so the size of how many items can be accommodated in the visual area is uncertain, and the end position cannot be determined finally. The idea of this point is actually very simple, listen to me

The previous details are basically the same as the contour height. Let’s focus on how to determine the size process

Specific ideas

  1. When we save the corresponding Item to the childrenSet,We need to set another set: "childHeightSet", which holds the itemHeight of the item.
  2. ChildHeightSet and childrenSet correspond one to one, that is, the same subscript, the value of the childrenHeightSet is the height of the value of the childrenSet. We can do this with this feature
  3. Instead of using scrollTop/itemHeight alone, compare the first n cumulative values of scrollTop and childrenHeightSet. When scrollTop is greater than the sum, the childrenSet is not at the truncated position; ScorllTop <= summation indicates that the current item has been slid to the top of the visible area, and the start value is the current subscript value
 function getStart(scrollTop) {
   var height = 0
   var start = 0
   var i = 0
   while(true) {
     const currentItem = childrenHeight[i]
     if (currentItem) {
       height += currentItem
       if (height >= scrollTop) {
         start = i
         break}}else {
       break
     }
     i++
   }

   return start
 }
Copy the code

4. To determine size, we need to screenClientHeight the current visual area and then compare the start in childrenHeightSet. When screenClientHeight is greater than the sum, screenClientHeight is not in the end position for childrenSet. If screenClientHeight <= summation, then the current item is already at the bottom of the visible range, and the end value is the current subscript value, thus resolving the non-isometric virtual list problem.

conclusion

Thanks for watching!

The source address

Virtual list