I’m sure you’ve all experienced the pain of rendering a long list or page. Long lists and pages can have a significant impact on the speed of the first screen rendering and make scrolling a bit awkward.

I recently ran into this problem and found that virtual scrolling was a popular solution in addition to directly using paging, so I reworked the virtual scrolling wheel in VUE as well. Virtual scrolling simply renders what is currently visible in the browser, dynamically calculates what is displayed based on where the user slides the scroll bar, and fills the rest with white space to give the user the illusion of a long list.

The wheel is not as complicated as you might think, but here’s how it’s made, and if you’re working or learning in the same situation, you can use this solution. The following is my implementation of virtual rolling a simple demo, online demo and source code.

Dom structure

The core DOM structure of virtual scrolling is essentially a simple list, which can be described in VUE as the following code

<div style="overflow-y: scroll; height: 300px;" @scroll="handleScroll">
    <div v-for="item in items" :key="`${item.id}`">
        <slot :data="item">
        </slot>
    </div>
</div>
Copy the code

Vue scoped Slot is used here to handle the user’s custom DOM content and the incoming data of custom DOM content. If there is no scoped slot, we can also do this by having the user define a specific render function in the incoming data object when the data is passed in.

With this list structure of customizable DOM, and a layer of scrollable, fixed-height containers on top, we have a base DOM of all list-like components. So the next thing to do is to fill in the scroll height beyond the visual list. There are many ways to do this, such as defining

above and below the list, and changing the height of

to control the total height; For example, control the padding-top and padding-bottom of a list; For example, if the height of the list is set to the sum of the heights of all elements, it is also possible to define position to enable top…… So now that we have this DOM structure, we can evaluate what’s displayed.

Listen for scrolling events to update list contents (handleScroll method)

1. Calculate the visual list range

The first thing we can determine is that the visual range of the list is a contiguous array of contents, so the calculation is simplified to find the start and end points of the contiguous array of contents. The start and end points depend on two pieces of information: the specific y coordinates of each item in the list, and the start and end points of the current visual range. The y-coordinate of each item in the list can be obtained by adding the height of each item in a loop, as shown below

The start point s of the current visual range is the scrollTop property of the list container, and the end point E is S plus the height of the list container. Now we have all the information we need to compute the starting and ending points of the array. The starting point of the array is to find an item in the y coordinates of all items that is no greater than s in visual range. The ending point of the array is to find an item in the y coordinates of all items that is no less than E, as shown below

When each item of the array is not fixed height, we use dichotomy (see source code for details) to find the upper and lower bounds of the array; When each term of the array is fixed height, we can get the upper bound by dividing s by the floor of each term, and the lower bound by dividing e by the floor of each term (ceil). The slice method is then used to obtain the final array of elements to display.

It may be noted that even though we use dichotomy O(logN) and direct computation O(1) instead of normal traversal, the slice method increases the overall complexity to O(N), so this optimization is only marginally faster in the case of finite arrays. So in practice, will we have an array so large that we can ignore the boost O(logN) and O(1) give us? The answer is no, because browsers’ memory limits on pages make it difficult to encounter such an array in real life.

2. Calculate the upper and lower filling height

With the upper and lower bounds of the array, the calculation of the padding height is actually very intuitive. Let’s set the padding-top and padding-bottom properties of the list as an example, as shown in the figure

Page mode

Many times we need to optimize not a long list, but a long page, so what changes to the above calculation method?

First of all we need to change the visual range of the calculation method of s to the starting point and end point e, the starting point for page visual range is window. PageYOffset | | document. The documentElement. ScrollTop; End point is the starting point and the height of the visible range, the height calculation window. We use innerHeight | | document. The documentElement. ClientHeight, but please note that these two attributes in page have a scroll bar when the return value is different, The innerHeight contains the height of the scroll bar, the clientHeight does not contain the height of the scroll bar.

Once we’ve calculated our visual range, we also need to adjust our array y coordinate. The original array y coordinates were relative to the scroll container. Now we need to adjust the array y coordinates relative to the page. There are two adjustment methods: one is to add the offsetTop attribute of the rolling container when calculating the Y coordinate; Second, you can subtract the offsetTop attribute of the scroll container when calculating the start point S and end point E of the visual range.

After adjusting the coordinates, we also need to remove the height and overflow-y attributes of the scroll container to allow the container to grow freely, and transfer the Scroll event of the scroll container to the window object, so as to realize the virtual scrolling of the page. With the page pattern, we can do this kind of optimization for any long page with a fixed height block layout.

More places to optimize

1. Scroll display optimization

The render will flicker when scrolling to refresh data too often, and we need to control the rate of updating the list by calling requestAnimationFrame to generate smooth scrolling animations.

2. List caching

Vue takes care of some of the list updates here, such as reusing previously rendered nodes for list updates in small array changes caused by scrolling. If you don’t use a similar framework, you’ll need to handle this part of the reuse logic yourself.

In addition, we can cache the rendering content within a certain range directly. For example, we can limit the number of cache nodes, and directly use the cache nodes when there is a cache hit during scrolling. If there is no hit and the cache node is full, certain cache replacement strategy can be used. For example, replace the least frequently used (LFU) cache node with a new node. Such list caching is used to re-optimize small scrollthroughs.

3. List recycling

Our current practice is to constantly destroy and recreate the DOM as we scroll, and while creating and destroying the DOM each time is not expensive, it still takes up a portion of the browser’s performance.

As each element of the list is by unified dom template or rendering function during rendering, we can through the list of recovery methods, the beyond visual range of dom node recovery, then the new data into the recycling of the dom node, finally put recovery after the update data node to go back to the list, the following figure. List recycling keeps the total number of DOM nodes in a very low range and eliminates the overhead of creating and destroying the DOM.

Finally, thank you for reading, if you have any comments, suggestions or front-end related questions are welcome to communicate with me, this is my Github, have a nice day! 🙂

A link to the

A VUE virtual scroll component that considers X-axis scrolling

Vue virtual scroll component added to list caching and list reclamation