background

The e server returns a large amount of data at once, how does the front-end display?

Solution:

  • Server-side perspective: paging
  • Front-end perspective: Child thread implementation via Web Worker (use Worker to open up a child thread independent of the main thread to do a lot of calculations, so that the page does not get stuck).
  • Virtual list
  • Partial rendering

How to Use Worker

  • Check whether the browser supports Web workers
if(typeof(Worker) ! = ='undefined'{/ /do something
}else {
    // sorry
}
Copy the code
  • Basic usage

The main thread uses the new command to call the Worker() constructor to create a new Worker thread.

var worker = new  Worker(worker.js)
Copy the code

The argument to the Worker() constructor is a script file that is the task to be performed by the Worker thread. Since the Worker cannot read local files, the script must come from the network. If the download does not succeed (such as a 404 error), the Worker silently fails.

The main thread then calls the worker.postMessage() method to send a message to the worker.

worker.postMessage('Hello World');
worker.postMessage({method: 'echo', args: ['Work']});
Copy the code

The argument to the worker.postmessage () method is the data that the main thread passes to the worker. It can be a variety of data types, including binary.

The main thread then specifies a listener through worker. onMessage to receive the message from the child thread.

worker.onmessage = function (event) {
  console.log('Received message ' + event.data);
  doSomething();
}

function doSomething{worker.postmessage () {worker.postmessage () {worker.postmessage ('Work done! ');
}
Copy the code

In the above code, the data attribute of the event object can obtain the data sent by the Worker.

After the Worker completes the task, the main thread can close it.

worker.terminate();
Copy the code

Virtual list

Assuming 10,000 records need to be rendered simultaneously, our screen visibility area is 500px high and list items are 50px high, then we can only see 10 list items on screen at most, so we only need to load 10 items on the first rendering.

After the first load, we can analyze the list items that should be displayed in the visible area of the screen by calculating the current scroll value when scrolling occurs.

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.

Implementation resolution:

  • Calculate the startIndex of the current viewable region
  • Calculate the endIndex of the current viewable region
  • Calculates the data for the current viewable area and renders it to the page
  • Calculate the startIndex data offset startOffset in the entire list and set it to the list

The code is as follows:

<template>
  <div ref="list" :style="{height}" class="infinite-list-container" @scroll="scrollEvent($event)">
    <div ref="phantom" class="infinite-list-phantom"></div>
    <div ref="content" class="infinite-list">
      <div class="infinite-list-item" ref="items" :id="item._index" :key="item._index" v-for="item in visibleData">
        <slot ref="slot" :item="item.item"></slot>
      </div>
    </div>
  </div>
</template>


<script>

export default {
  name:'VirtualList', props: {// all listData. ListData :{type:Array, default:()=>[]}, // estimatedItemSize:{type:Number,
      required: true}, // bufferScale bufferScale:{type:Number, default:1}, // container height 100px or 50vh height:{type:String,
      default:'100%'
    }
  },
  computed:{
    _listData() {return this.listData.map((item,index)=>{
        return {
          _index:`_${index}`,
          item
        }
      })
    },
    visibleCount() {return Math.ceil(this.screenHeight / this.estimatedItemSize);
    },
    aboveCount() {return Math.min(this.start,this.bufferScale * this.visibleCount)
    },
    belowCount() {return Math.min(this.listData.length - this.end,this.bufferScale * this.visibleCount);
    },
    visibleData() {let start = this.start - this.aboveCount;
      let end = this.end + this.belowCount;
      returnthis._listData.slice(start, end); }},created(){
    this.initPositions();
    window.vm = this;
  },
  mounted() {
    this.screenHeight = this.$el.clientHeight;
    this.start = 0;
    this.end = this.start + this.visibleCount;
  },
  updated(){
    this.$nextTick(function () {
      if(! this.$refs.items || ! this.$refs.items.length){
        return; } this.updateItemsSize(); // Update the list heightlet height = this.positions[this.positions.length - 1].bottom;
      this.$refs.phantom.style.height = height + 'px'// Update the true offset this.setstartoffSet (); })},data() {
    return{// screenHeight:0, // start index :0, // end index :0,}; }, methods: {initPositions(){ this.positions = this.listData.map((d,index)=>({ index, height:this.estimatedItemSize, top:index * this.estimatedItemSize, bottom:(index+1) * this.estimatedItemSize }) ); }, // get the list start index getStartIndex(scrollTop = 0){// binary searchreturn this.binarySearch(this.positions,scrollTop)
    },
    binarySearch(list,value){
      let start = 0;
      let end = list.length - 1;
      let tempIndex = null;

      while(start <= end){
        let midIndex = parseInt((start + end)/2);
        let midValue = list[midIndex].bottom;
        if(midValue === value){
          return midIndex + 1;
        }else if(midValue < value){
          start = midIndex + 1;
        }else if(midValue > value){
          if(tempIndex === null || tempIndex > midIndex){ tempIndex = midIndex; } end = end - 1; }}returntempIndex; }, // Get the current size of the list itemupdateItemsSize() {let nodes = this.$refs.items;
      nodes.forEach((node)=>{
        let rect = node.getBoundingClientRect();
        let height = rect.height;
        let index = +node.id.slice(1)
        let oldHeight = this.positions[index].height;
        letdValue = oldHeight - height; // There is a differenceif(dValue){
          this.positions[index].bottom = this.positions[index].bottom - dValue;
          this.positions[index].height = height;
          for(letk = index + 1; k<this.positions.length; k++){ this.positions[k].top = this.positions[k-1].bottom; this.positions[k].bottom = this.positions[k].bottom - dValue; }}})}, // Get the current offsetsetStartOffset() {let startOffset;
      if(this.start >= 1){
        let size = this.positions[this.start].top - (this.positions[this.start - this.aboveCount] ? this.positions[this.start - this.aboveCount].top : 0);
        startOffset = this.positions[this.start - 1].bottom - size;
      }else{
        startOffset = 0;
      }
      this.$refs.content.style.transform = `translate3d(0,${startOffset}Px,0) '}, // scroll eventsscrollEvent() {// Current scroll positionlet scrollTop = this.$refs.list.scrollTop;
      // letStartBottom = this.positions[this.start -] // This. Start = this.getStartIndex(scrollTop); This.end = this.start + this.visiblecount; // The offset of this.setStartoffSet (); }}}; </script> <style scoped> .infinite-list-container { overflow: auto; position: relative; -webkit-overflow-scrolling: touch; } .infinite-list-phantom { position: absolute; left: 0; top: 0; right: 0; z-index: -1; } .infinite-list { left: 0; right: 0; top: 0; position: absolute; } .infinite-list-item { padding: 5px; color:# 555;
  box-sizing: border-box;
  border-bottom: 1px solid # 999;
  /* height:200px; */
}

</style>
Copy the code
<div id="app">
  <VirtualList :listData="data" :itemSize="100"/>
</div>

let d = [];
for (let i = 0; i < 1000; i++) {
  d.push({ id: i, value: i });
}

export default {
  name: "App".data() {
    return {
      data: d
    };
  },
  components: {
    VirtualList
  }
};
Copy the code