preface

In mobile projects, we usually use lazy loading to load lists, which has the obvious advantage of not having to request the data all at once, but using Ajax to dynamically pull the following data from the server when the user drops down to the bottom. The problem with this is that if the user goes crazy with the pull-down, it will cause the browser to create redundant nodes, which will be redundant, and the VUE will diff as many nodes as you have, which will lead to redundant performance and memory usage. Imagine solving this problem if we could render only nodes in the user’s visual area, which is the background for virtual scrolling.

The principle of virtual scrolling

First of all, we want to know, the virtual rolling is to use Vue v – for implementation, it also explains, virtual scroll is rendering viewing area, only then we viewing area node content is bound to be changed by the user of the change of the scroll bar, suppose that a page can show n nodes, so how do we make this n need to change the node with the scroll bar to move?

Using CSStransform:translateY(), we just need to make the n nodes follow the scroll bar, where we scroll, where the node is replaced.

To implement virtual scrolling you only need to know the following:

  1. How many items can be displayed on a page?
  2. Which node should I start rendering from?
  3. When do you render?
  4. How to apply colours to a drawing

How many items can a page display?

Page size = page size (clientHeight)/ single item size

volume=Math.ceil(clientHeight/itemHeight)+4;

Why do we have a plus 4 here, and we’ll talk about that later

Which node should I start rendering from?

Let’s assume that the scrollbar is now at x, can we calculate how many nodes this x height can hold, and then figure out which node to start rendering from? The answer is yes, js provides us with the property scrollTop to get the height of the scroll bar.

    getCurStart(scrollTop){
      // How many books are there
      return Math.floor(scrollTop/(itemHeight));
    }
Copy the code

When do you render?

Rendering time is also very simple. We need to render when the first node in the list is completely involved, because at this point the node is completely invisible, we need to top it down and then render it into the next item

As shown in the image above, when the 1 is rolled out (completely out of our viewable area), we use the translateY of CSS to top it off and render it to 2. You will notice that there is an extra node outside the viewable area. To ensure the continuity of the slide, you can set up several redundant nodes.

How to apply colours to a drawing

This is the core code. There is a problem here, because JS does not respond to the high frequency callback every time, if you do not get an offset divisible by itemHeight, you will most likely see that the offset of the first node is not 0 when you pull back.

    onScroll(){
      // The scrollTop constant records the height of the current scroll
      const scrollTop=this.$refs.list.scrollTop;

      const start=this.getCurStart(scrollTop);
      // Compare the last start node to see if it has changed, and then re-render the list
      if(this.start! =start){// We need to get a number divisible by itemHeight as the offset of the item
        const offsetY = scrollTop - (scrollTop % this.itemHeight);
        // Use slice to get the part that needs to be rendered
        this.renderList=this.list.slice(start,this.start+this.volume);
        TranslateY transform:translateY(${top}px)
        this.top=offsetY;
      }
      this.start=start;
    },
Copy the code

Optimization of processing

OnScroll is a high-frequency trigger callback. In order to save performance consumption, we need to limit it to trigger at least 50ms. The following are the encapsulation throttling functions.

export default function(fn, delay) {
    let lock = false;
    return (. args) = > {
        if (lock)
            return;
        // Enter the lock
        lock = true;
        setTimeout(() = > {
            fn.apply(this, args);
            // The account is unlocked
            lock = false; }, delay); }}Copy the code

The complete code

<template>
    <div class="list" @scroll="scrollHandle" ref="list">
        <div class="item" v-for="(item,index) in renderList" :key="index"  :style="`height:${itemHeight}px; line-height:${itemHeight}px; transform:translateY(${top}px)`">
          {{item}}
        </div>
    </div>
</template>
<script>
import throttle from '@/utils/throttle';
export default {
  name: 'App'.data() {
    return {
      list: [].// Complete list
      itemHeight:60.// The height of each term
      renderList: [].// The list to render
      start:0.// Start rendering position
      volume:0.// Page capacity: how many nodes can fit
      top:0,
      scroll,// Used to initialize throttling}},mounted() {
    this.initList();
    const cHeight=document.documentElement.clientHeight
    // Compute page can hold several nodes and set up four nodes for redundancy
    this.volume=Math.ceil(cHeight/this.itemHeight)+4;
    // Set the list to render to the maximum number of elements it can hold
    this.renderList=this.list.slice(0.this.volume);
    // Initialize the throttling function to fire at least 50 milliseconds
    this.scroll=throttle(this.onScroll,50);
  },
  methods: {
    // Initialize the list with a loop of 500 entries
    initList(){
      for(let i=0; i<500; i++){this.list.push(i); }},scrollHandle(){
      this.scroll();
    },
    onScroll(){
      // The scrollTop constant records the height of the current scroll
      const scrollTop=this.$refs.list.scrollTop;

      const start=this.getCurStart(scrollTop);
      // Compare the last start node to see if it has changed, and then re-render the list
      if(this.start! =start){// We need to get a number divisible by itemHeight as the offset of the item
        const offsetY = scrollTop - (scrollTop % this.itemHeight);
        // Use slice to get the part that needs to be rendered
        this.renderList=this.list.slice(start,this.start+this.volume);
        TranslateY transform:translateY(${top}px)
        this.top=offsetY;
      }
      this.start=start;
    },
    getCurStart(scrollTop){
      // How many books are there
      return Math.floor(scrollTop/(this.itemHeight)); }}},</script>

<style>* {margin: 0;
  padding: 0;
}
.list{
  height: 100vh;
  overflow: scroll;
}
.item{
  text-align: center;
  width: 100%;
  box-sizing: border-box;
  border-bottom: 1px solid lightgray;
}
</style>

Copy the code

conclusion

There are mistakes welcome to point out, the whole article down pure handwriting, the first time to write a blog, please excuse me.