When you have a large amount of data to display, you can use pagination, but the user doesn’t have a coherent view of the information. At this point, the data can be cached locally and displayed in the form of a virtual list. Here, a simple virtual list is developed using Vue3.

Train of thought

Just to give you an idea, it’s not the only way to do it, it’s not the best way to do it, but the principle is pretty much the same, and it’s relatively easy to understand.

Page structure

<div id="app">
  <div class="container">
    <div class="empty"></div>
    <ul class="list">
      <li class="item"></li>
    </ul>
  </div>
</div>
Copy the code

  1. Initialize container: Give a containercontainerSet the height, list will be displayed in the container, set overflow scrolling.
  2. Open container: Set a fixed row height that can be set according to the listlengthCalculate the actual height of the list and set it to adivHere is calledempty, so that the container can already start scrolling, but there is no data in the page.
  3. Render list: Calculate how many containers can be displayed based on a fixed line heightitem, the page always shows the fixed number, givecontainerSet up thedisplay: flex;And letlistemptySide by side, becauseemptyOnly the height has no width, so it is invisible to the user and only serves to spread outcontainerThe role of.

  1. Container rolling: When rollingcontainerWhen, according toscrollTopFigure out how many there areitemRolled away, set toslicestart, recalculate the part that should be cut from the presentation and givelistSet up andscrollTopThe sametranslateYSo that the top of the list is always aligned with the top of the container.

Code implementation

const oriData = Array.from({ length: 1000 }, (v, k) = > k)
const itemHeight = 20
const emptyHeight = itemHeight * oriData.length
const containerHeight = window.innerHeight
const itemCount = Math.ceil(containerHeight / itemHeight)
Copy the code
  • oriDataGenerates an array of length 1000 as fake data
  • itemHeightSet the row height to 20, that is, the height occupied by each data item
  • emptyHeightThe actual height that the list should occupy
  • containerHeightHeight of the container
  • itemCountThe number of pages displayed at a timeceilBecause let’s say the last line doesn’t show oneitemShow it, too, or there will be an empty space at the bottom of the page
const container = ref(null)
const start = ref(0)
const translateY = ref(0)
const listData = computed(() = > {
  return oriData.slice(start.value, start.value + itemCount + 1)})Copy the code
  • translateYAs the page scrolls,listMove too, or it will disappear from the screen
  • listDataThe data that is always displayed on the page, and the reason for this is that the data in the list changes as you scroll through the screen
onMounted(() = > {
  container.value.addEventListener('scroll'.e= > {
    const { scrollTop } = e.target
    start.value = Math.floor(scrollTop / itemHeight)
    translateY.value = scrollTop + 'px'})})Copy the code

When the container rolls, get the scrollTop, assign it to the translateY so that the top of the list is always opposite the top of the container. And figure out how many items we have to scroll by, and the reason we use floor is because if we scroll 39px we should scroll by one item instead of two. The calculation properties above recalculate the array to intercept based on changes in start, and relying on Vue makes this easier.

The complete code

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
    }

    .container {
      overflow: auto;
      display: flex;
    }
  </style>
</head>
<div id="app">
  <div class="container" ref="container" :style="{ height: containerHeight }">
    <div class="empty" :style="{ height: emptyHeight }"></div>
    <ul class="list" :style="{ transform: `translateY(${translateY})` }">
      <li v-for="item in listData" :key="item" class="item" :style="{ height: itemHeight }">{{ item }}</li>
    </ul>
  </div>
</div>

<body>
  <script src="https://unpkg.com/vue@next"></script>
  <script>
    const { ref, onMounted, computed } = Vue
    const App = {
      setup() {
        const oriData = Array.from({ length: 1000 }, (v, k) = > k)
        const itemHeight = 20
        const emptyHeight = itemHeight * oriData.length
        const containerHeight = window.innerHeight
        const itemCount = Math.ceil(containerHeight / itemHeight)
        const container = ref(null)
        const start = ref(0)
        const translateY = ref(0)
        const listData = computed(() = > {
          return oriData.slice(start.value, start.value + itemCount + 1)
        })
        onMounted(() = > {
          container.value.addEventListener('scroll'.e= > {
            const { scrollTop } = e.target
            start.value = Math.floor(scrollTop / itemHeight)
            translateY.value = scrollTop + 'px'})})return {
          listData,
          container,
          translateY,
          containerHeight: containerHeight + 'px'.itemHeight: itemHeight + 'px'.emptyHeight: emptyHeight + 'px'
        }
      }
    }
    Vue.createApp(App).mount('#app')
  </script>

</body>

</html>
Copy the code

Write in the back

In fact, according to the rendering rules of the virtual DOM, this example performs better without adding a key to the item than with the key