What is a long list?

In front-end business development, there will be some lists with large amount of data that cannot be loaded by pagination. Such lists are generally called long lists. A fully rendered long list is basically difficult to meet the requirements of the business. A non-fully rendered long list generally has two ways:

  • Lazy rendering:This is the usual infinite scroll, rendering one part at a time (say 15), and then rendering another part as the rest of the scroll reaches the courseware area
  • Visual area rendering:Render only the visible part, not the invisible part

Virtual lists are optimized for visual area rendering

The realization principle of virtual list

A virtual list is a long list optimization solution, which is a visual area rendering list, where you need to know two concepts:

  • Scrollable area:If you have 1000 data items and each list item is 30 in height, then the height of the scrollable area is 1000 by 30. When the user changes the current scroll value of the list’s scroll bar, the contents of the visible area change
  • Visible area:For example, if the list height is 300 and there is a vertical scroll bar to scroll to the right, the visually visible area is the visible area

Use an array to hold the positions of all list elements. Render only list elements in the visual area. When the visual area is scrolling, calculate which elements should be rendered in the visual area based on the offset size of the scroll and the positions of all list elements

Since only list items within the viewable area are rendered, the Html structure is designed to maintain the height of the list container and trigger the normal scrolling:

<div class="infinite-list-container">
    <! -- placeholder div, set height to the total height of the list, for normal display of the scroll bar -->
    <div class="infinite-list-phantom"></div>
    <! -- List, dynamically changing its transformY -->
    <div class="infinite-list">
      <! -- item-1 -->
      <! -- item-2 -->
      <! -... -->
      <! -- item-n -->
    </div>
</div>
Copy the code

Reference implementation

The realization of virtual list is to deal with the change of the visible area after the scroll bar is rolled. The specific implementation steps are as follows:

  1. StartIndex to calculate the start data of the current visible region
  2. Computes the endIndex of the current visible region end data
  3. Calculates the data for the current visible area and renders it to the page
  4. Calculate the startIndex data offset startOffset in the entire list and set it to the list

Imagine for a moment that each list item is 30px high. Under this benchmark, the core JS code should be no more than ten lines. However, rendering and updating of visible areas can be done

Code implementation

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Simple implementation of virtual lists</title>
  </head>
  <style>
    .list-view {
      height: 400px;
      overflow: auto;
      position: relative;
      border: 1px solid #aaa;
    }

    .list-view-phantom {
      /* Using the invisible area, prop up the list so that the list scroll bar appears */
      position: absolute;
      left: 0;
      top: 0;
      right: 0;
      z-index: -1;
    }

    .list-view-content {
      left: 0;
      right: 0;
      top: 0;
      position: absolute;
    }

    .list-view-item {
      padding: 5px;
      color: # 666;
      line-height: 30px;
      box-sizing: border-box;
    }

    [v-cloak] {
      display: none;
    }
  </style>

  <body>
    <div id="app" v-cloak>
      <div class="list-view" ref="scrollBox" @scroll="handleScroll">
        <div class="list-view-phantom" :style="{ height: contentHeight }"></div>
        <ul ref="content" class="list-view-content">
          <li
            class="list-view-item"
            :style="{ height: itemHeight + 'px' }"
            v-for="item in visibleData"
          >
            {{ item }}
          </li>
        </ul>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      new Vue({
        el: "#app".computed: {
          contentHeight() {
            return this.data.length * this.itemHeight + "px"; }},mounted() {
          for (let i = 0; i < 100; i++) {
            this.data.push(i);
          }
          this.updateVisibleData();
        },
        data() {
          return {
            data: [].itemHeight: 30.visibleData: [],}; },methods: {
          updateVisibleData(scrollTop = 0) {
            // The number to display
            const visibleCount = Math.ceil(
              this.$refs.scrollBox.clientHeight / this.itemHeight
            );
            // Start index, container scrollTop/height of an item
            const start = Math.floor(scrollTop / this.itemHeight);
            // End index, start index + number to display
            const end = start + visibleCount;
            // Dynamically change array data while scrolling
            this.visibleData = this.data.slice(start, end);
            // Set the transfromY of the list to start indexing * the height of an item
            this.$refs.content.style.webkitTransform = `translate3d(0, ${
              start * this.itemHeight
            }px, 0)`;
          },
          handleScroll() {
            const scrollTop = this.$refs.scrollBox.scrollTop;
            this.updateVisibleData(scrollTop); ,}}});</script>
  </body>
</html>
Copy the code

Extension: Use IntersectionObserver instead of monitoring scroll events

In the previous paper, we used the way of listening to scroll event to trigger the update of data in the visual area. When the scroll occurs, scroll event will be triggered frequently, which will cause the problem of double calculation in many cases. There is undoubtedly a waste in terms of performance.

IntersectionObserver can replace and monitor scroll events with IntersectionObserver, and IntersectionObserver can monitor whether target elements appear in the visible area and update the visible area data in the monitored callback events. Moreover, the listening callback of IntersectionObserver is triggered asynchronously and does not trigger with the rolling of the target element, so the performance consumption is very low.