Background:

Pages contain a large amount of data, which takes a long time to render, affecting user experience. In page development, we sometimes encounter business situations where the list data cannot be paginated to load or can be paginated, but the current page data is very large (including many images). For this, we call this kind of list a long list. For example, in some kanban data or in some foreign exchange trading systems, the front end will display the user’s position status (gain, loss, income, etc.) in real time. At this time, the user’s position list generally cannot be pagined.

In the high Performance Rendering of 100,000 pieces of data (time sharding) article, we mentioned that we can use time sharding to render long lists, but this method is more suitable for the DOM structure of list items is very simple. This article shows you how to use virtual lists to load large amounts of data simultaneously

Usage scenarios

  1. You cannot load list data using pagination;
  2. It can be paginated, but the page contains a lot of data or contains a lot of image data;
  3. The amount of data in the drop-down list is large.

Implementation scheme

What is a virtual list?

Virtual list is actually an implementation of on-demand display, that is, rendering only the visible area, not rendering or partial rendering of the data in the non-visible area, so as to achieve extremely high rendering performance.

Let’s say we have 10,000 records that need to be rendered at the same timeThe visible areaThe height of500px, and the height of the list item is50pxAt this point, we can only see 10 list items on screen at most, so we only need to load 10 items on the first rendering.

After first loading, and then analyzing when scrolling occurs, we can know by calculating the current scroll value at this time on the screenThe visible areaList items that should be displayed.

If the scroll occurs and the scroll bar is 150px from the top, we know that the list items in the visible area are items 4 through 13.

The implementation of the virtual list is actually loading only when the first screen loadsViewing areaList items needed in, dynamically computed when scrolling occursViewing areaList items within and willInvisible areaAn existing list item was deleted.

  • Calculate the currentViewing areaInitial data index (startIndex)
  • Calculate the currentViewing areaEnd data index (endIndex)
  • Calculate the currentVisible areaData and render it to the page
  • To calculatestartIndexThe offset position of the corresponding data in the entire liststartOffsetAnd set it to the list

Because it’s justViewing areaSo in order to maintain the height of the list container and trigger the normal scrolling, the Html structure is designed as follows:

<div class="infinite-list-container">
    <div class="infinite-list-phantom"></div>
    <div class="infinite-list">
      <!-- item-1 -->
      <!-- item-2 -->
      <!-- ...... -->
      <!-- item-n -->
    </div>
</div>
Copy the code
  • infinite-list-containerViewing areaThe container
  • infinite-list-phantomIs a placeholder in the container, height is the total list height, used to form scroll bars
  • infinite-listOf the list itemApply colours to a drawing area

Then, listen for the Scroll event of infinite-list-Container to obtain the scroll position scrollTop

  • Assume thatViewing areaThe height is fixedscreenHeight
  • Assume thatList eachThe height is fixeditemSize
  • Assume thatThe list of dataCall itlistData
  • Assume thatCurrent scroll positionCall itscrollTop

Then it can be calculated that:

  • Total list heightlistHeight = listData.length * itemSize
  • The number of list items that can be displayedvisibleCount = Math.ceil(screenHeight / itemSize)
  • The initial index of the datastartIndex = Math.floor(scrollTop / itemSize)
  • The end index of the dataendIndex = startIndex + visibleCount
  • The data in the list isvisibleData = listData.slice(startIndex,endIndex)

After scrolling, since the render area has been offset relative to the viewable area, I need to get an offset startOffset to offset the render area to the viewable area with style control.

  • The offsetstartOffset = scrollTop – (scrollTop % itemSize);

The resulting simple code looks like this:

<template>
  <div ref="list" class="infinite-list-container" @scroll="scrollEvent($event)">
    <div class="infinite-list-phantom" :style="{ height: listHeight + 'px' }"></div>
    <div class="infinite-list" :style="{ transform: getTransform }">
      <div ref="items"
        class="infinite-list-item"
        v-for="item in visibleData"
        :key="item.id"
        :style="{ height: itemSize + 'px',lineHeight: itemSize + 'px' }"
      >{{ item.value }}</div>
    </div>
  </div>
</template>
Copy the code
Export default {name:'VirtualList', props: {// All listData:{type:Array, default:()=>[]}, // Each height itemSize: {type: Number, default:200}}, computed:{// total listHeight listHeight(){return this.listdata.length * this.itemsize; }, visibleCount(){return math.ceil (this.screenheight/this.itemSize)}, ${this.startoffset}px,0) '; }, / / obtain real display list data visibleData () {return enclosing listData. Slice (enclosing start, Math. Min (this end, enclosing listData. Length)); } }, mounted() { this.screenHeight = this.$el.clientHeight; this.start = 0; this.end = this.start + this.visibleCount; }, data() {return {screenHeight:0, // offset startOffset:0, // start index start:0, // end index end:null,}; }, methods: {scrollEvent() {let scrollTop = this.$refs.list.scrollTop; This.start = math.floor (scrollTop/this.itemSize); This.end = this.start + this.visiblecount; This.startoffset = scrollTop - (scrollTop % this.itemSize); }}};Copy the code

We implemented a virtual list with text content dynamically pushing up list items, but we might find that the screen briefly went white when scrolling too fast.

To make the page scroll smoothly, we also need to render additional items above and below the visible area to give some buffer while scrolling, so we split the screen into three areas:

  • Above the visible area:above
  • Visual area:screen
  • Below the viewable area:below

Defining component propertiesbufferScale, for receivingBuffer datawithViewable datatheThe proportion

Props :{// bufferScale:{type:Number, default:1}}Copy the code

AboveCount can be obtained as follows:

aboveCount(){
  return Math.min(this.start,this.bufferScale * this.visibleCount)
}
Copy the code

BelowCount can be obtained as follows:

belowCount(){
  return Math.min(this.listData.length - this.end,this.bufferScale * this.visibleCount);
}
Copy the code

The real rendering data visibleData can be obtained as follows:

visibleData(){
  let start = this.start - this.aboveCount;
  let end = this.end + this.belowCount;
  return this._listData.slice(start, end);
}
Copy the code

We can also use some packaged components, such as InfiniteScroll

<InfiniteScroll initialLoad={false} pageStart={0} loadMore={loadFrequency === 0 && this.handleInfiniteOnLoad} hasMore={loading === 1 && hasMore} useWindow={false} pullDownToRefresh // loader={<div className="loader" key={0}>Loading ... </div>} >Copy the code

How virtual lists differ from lazy loading

Lazy loading and virtual list are actually a kind of implementation of delayed loading, with the same principle but slightly different scenarios:

  1. Lazy loading application scenarios tend to favor network resource requests, which solves the problem of long response time caused by excessive network resource requests.
  2. Virtual list is an optimization of long list rendering, to solve the problem of performance bottleneck caused by large amount of data rendering;

Online Demo and complete code:

Codesandbox. IO/s/virtualli…

The document reference: cloud.tencent.com/developer/a…