How front-end performance optimization renders large amounts of data onto the page

Recommended website LuckyCola for essential front-end tools (free picture bed, API, chatAI, etc.):
https://luckycola.com.cn/

1. Background

Most of the data interaction between front-end and back-end is in the form of interfaces. Sometimes the amount of data returned by the back-end to the front-end is very huge, and the front-end needs to render this data to the page view according to business needs, so the amount of data is huge. In this case, this consumption of front-end performance is also huge, so how can we handle this situation more efficiently?

2. Plan introduction

1. Direct rendering

The most straightforward way is to render all the data to the page at once. code show as below:

const renderList = async () => {
    
    
    const list = await getList()

    list.forEach(item => {
    
    
        const div = document.createElement('div')
        div.className = 'sunshine'
        div.innerHTML = `<img src="${
      
      item.src}" /><span>${
      
      item.text}</span>`
        container.appendChild(div)
    })
}
renderList()

Rendering 100,000 records at a time takes about 12 seconds, which is obviously not advisable.

2. Paging rendering through setTimeout

A simple optimization method is to paginate the data. Assuming that each page has a limit record, the data can be divided into Math.ceil (total/limit) pages. Afterwards, we can use setTimeout to render the pages sequentially, one page at a time.

const renderList = async () => {
    
    

    const list = await getList()

    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total / limit)

    const render = (page) => {
    
    
        if (page >= totalPage) return
        setTimeout(() => {
    
    
            for (let i = page * limit; i < page * limit + limit; i++) {
    
    
                const item = list[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${
      
      item.src}" /><span>${
      
      item.text}</span>`
                container.appendChild(div)
            }
            render(page + 1)
        }, 0)
    }
    render(page)
}

After paging, data can be quickly rendered to the screen, reducing page blank time.

3. Paging rendering through requestAnimationFrame

When rendering the page, we can use requestAnimationFrame instead of setTimeout, which can reduce the number of reflows and improve performance.

const renderList = async () => {
    
    
    const list = await getList()

    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total / limit)

    const render = (page) => {
    
    
        if (page >= totalPage) return

        requestAnimationFrame(() => {
    
    
            for (let i = page * limit; i < page * limit + limit; i++) {
    
    
                const item = list[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${
      
      item.src}" /><span>${
      
      item.text}</span>`
                container.appendChild(div)
            }
            render(page + 1)
        })
    }
    render(page)
}

The window.requestAnimationFrame() method tells the browser that you want to perform animation and requests the browser to call the specified function to update the animation before the next redraw. This method takes the callback as parameter to be called before redrawing.

4. Document fragments

Previously, every time a div element was created, the element would be inserted directly into the page via appendChild. But appendChild is an expensive operation.
In fact, we can create a document fragment first, create the div element, and then insert the element into the document fragment. After all div elements are created, insert the fragment into the page. Doing so can also improve page performance.


const renderList = async () => {
    
    
    console.time('time')
    const list = await getList()
    console.log(list)
    const total = list.length
    const page = 0
    const limit = 200
    const totalPage = Math.ceil(total / limit)

    const render = (page) => {
    
    
        if (page >= totalPage) return
        requestAnimationFrame(() => {
    
    

            const fragment = document.createDocumentFragment()
            for (let i = page * limit; i < page * limit + limit; i++) {
    
    
                const item = list[i]
                const div = document.createElement('div')
                div.className = 'sunshine'
                div.innerHTML = `<img src="${
      
      item.src}" /><span>${
      
      item.text}</span>`

                fragment.appendChild(div)
            }
            container.appendChild(fragment)
            render(page + 1)
        })
    }
    render(page)
    console.timeEnd('time')
}

5. Lazy loading

Although the backend returns so much data at once, the user's screen can only display limited data at the same time. So we can use a lazy loading strategy to dynamically render data based on the user's scroll position.
To get the user's scroll position, we can add an empty node blank at the end of the list. Whenever the viewport goes blank, it means the user has scrolled to the bottom of the web page, which means we need to continue rendering data.
At the same time, we can use getBoundingClientRect to determine whether the blank is at the bottom of the page.


<script setup lang="ts">
import {
    
     onMounted, ref, computed } from 'vue'
const getList = () => {
    
    
  // code as before
}
const container = ref<HTMLElement>() // container element
const blank = ref<HTMLElement>() // blank element
const list = ref<any>([])
const page = ref(1)
const limit = 200
const maxPage = computed(() => Math.ceil(list.value.length / limit))
// List of real presentations
const showList = computed(() => list.value.slice(0, page.value * limit))
const handleScroll = () => {
    
    
  if (page.value > maxPage.value) return
  const clientHeight = container.value?.clientHeight
  const blankTop = blank.value?.getBoundingClientRect().top
  if (clientHeight === blankTop) {
    
    
    // When the blank node appears in the viewport, the current page number is incremented by 1
    page.value++
  }
}
onMounted(async () => {
    
    
  const res = await getList()
  list.value = res
})
</script>

<template>
  <div id="container" @scroll="handleScroll" ref="container">
    <div class="sunshine" v-for="(item) in showList" :key="item.tid">
      <img :src="item.src" />
      <span>{
    
    {
    
     item.text }}</span>
    </div>
    <div ref="blank"></div>
  </div>
</template>

Guess you like

Origin blog.csdn.net/qq_48896417/article/details/131266808