Frontend Development 21 min read

Efficient Rendering of Large Datasets in Vue.js Frontend Applications

This article demonstrates several techniques—including server‑side data simulation, batch rendering with timers, requestAnimationFrame, pagination components, infinite scroll, and virtual lists—to efficiently display and interact with 100,000 records in a Vue.js front‑end without causing UI freezes or performance degradation.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Efficient Rendering of Large Datasets in Vue.js Frontend Applications

Simulating 100,000 Records

Use express to create an API endpoint that returns an array of 100,000 objects, each containing id , name , and value fields.

route.get("/bigData", (req, res) => {
res.header('Access-Control-Allow-Origin', '*'); // allow CORS
let arr = [] // store 100k items
for (let i = 0; i < 100000; i++) {
arr.push({
id: i + 1,
name: '名字' + (i + 1),
value: i + 1,
})
}
res.send({ code: 0, msg: '成功', data: arr }) // return data
})

Send Request on Button Click

Bind a click handler to an el-button that triggers an asynchronous request and stores the result in the component state.

<el-button :loading="loading" @click="onClick">点击请求加载</el-button>
<el-table :data="arr">
<el-table-column type="index" label="序" />
<el-table-column prop="id" label="ID" />
<el-table-column prop="name" label="名字" />
<el-table-column prop="value" label="对应值" />
</el-table>
data() {
return {
arr: [],
loading: false,
};
},
async onClick() {
// send request, assign data to arr
}

Solution 1: Direct Rendering (Not Recommended)

Fetching all data and assigning it directly to the table causes the page to freeze because the browser must render 100,000 DOM nodes at once.

async onClick() {
this.loading = true;
const res = await axios.get("http://ashuai.work:10000/bigData");
this.arr = res.data.data;
this.loading = false;
}

Solution 2: Batch Rendering with setTimeout

Split the 100k array into small chunks (e.g., 10 items per chunk) and render each chunk sequentially using setTimeout , which prevents the UI from freezing.

function averageFn(arr) {
let i = 0;
let result = [];
while (i < arr.length) {
result.push(arr.slice(i, i + 10));
i = i + 10;
}
return result;
}
async onClick() {
this.loading = true;
const res = await axios.get("http://ashuai.work:10000/bigData");
this.loading = false;
let twoDArr = averageFn(res.data.data);
for (let i = 0; i < twoDArr.length; i++) {
setTimeout(() => {
this.arr = [...this.arr, ...twoDArr[i]];
}, 1000 * i); // interval ~16.6 ms (1000/60)
}
},

Solution 3: Rendering with requestAnimationFrame

Use requestAnimationFrame instead of setTimeout to align rendering with the browser’s repaint cycle, achieving smoother updates.

async onClick() {
this.loading = true;
const res = await axios.get("http://ashuai.work:10000/bigData");
this.loading = false;
let twoDArr = averageFn(res.data.data);
const use2DArrItem = (page) => {
if (page > twoDArr.length - 1) return;
requestAnimationFrame(() => {
this.arr = [...this.arr, ...twoDArr[page]];
use2DArrItem(page + 1);
});
};
use2DArrItem(0);
},

Solution 4: Front‑End Pagination with Element‑UI

Leverage the built‑in pagination component of Element‑UI to display a subset of rows per page.

<template>
<div class="box">
<el-table height="240" :data="showTableData" border style="width: 100%">
<el-table-column prop="name" label="姓名" width="180"></el-table-column>
<el-table-column prop="age" label="年龄" width="180"></el-table-column>
<el-table-column prop="home" label="家乡"></el-table-column>
</el-table>
<el-pagination
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageIndex"
:page-size="pageSize"
:page-sizes="[2, 4, 6, 10]"
:total="total"
></el-pagination>
</div>
</template>
<script>
export default {
data() { return { pageIndex: 1, pageSize: 4, total: 0, allTableData: [], showTableData: [] }; },
mounted() { /* fetch all data and init pagination */ },
methods: {
getShowTableData() { let begin = (this.pageIndex - 1) * this.pageSize; let end = this.pageIndex * this.pageSize; this.showTableData = this.allTableData.slice(begin, end); },
handleCurrentChange(val) { this.pageIndex = val; this.getShowTableData(); },
handleSizeChange(val) { this.pageIndex = 1; this.pageSize = val; this.getShowTableData(); }
}
};
</script>

Solution 5: Scroll‑Bottom Auto‑Load

Detect when the scroll reaches the bottom using either scrollTop + clientHeight >= innerHeight or a MutationObserver , then load the next batch of data.

scrollTop + clientHeight >= innerHeight
new MutationObserver()

Solution 6: Virtual List (Windowing)

Render only the rows that are visible in the viewport and recycle DOM nodes as the user scrolls, dramatically reducing the number of elements the browser must manage.

<template>
<div class="virtualListWrap" ref="virtualListWrap" @scroll="handleScroll" :style="{ height: itemHeight * count + 'px' }">
<div class="placeholderDom" :style="{ height: allListData.length * itemHeight + 'px' }"></div>
<div class="contentList" :style="{ top: topVal }">
<div v-for="(item, index) in showListData" :key="index" class="itemClass" :style="{ height: itemHeight + 'px' }">{{ item.name }}</div>
</div>
<div class="loadingBox" v-show="loading"><i class="el-icon-loading"></i> loading...</div>
</div>
</template>
<script>
export default {
data() { return { allListData: [], itemHeight: 40, count: 10, start: 0, end: 10, topVal: '0px', loading: false }; },
computed: { showListData() { return this.allListData.slice(this.start, this.end); } },
async created() { this.loading = true; const res = await axios.get("http://ashuai.work:10000/bigData"); this.allListData = res.data.data; this.loading = false; },
methods: { handleScroll() { const scrollTop = this.$refs.virtualListWrap.scrollTop; this.start = Math.floor(scrollTop / this.itemHeight); this.end = this.start + this.count; this.topVal = scrollTop + 'px'; } }
};
</script>

Solution 7: VXETable Virtual Scrolling

For table‑based data, the vxe-table component provides built‑in virtual scrolling that can handle up to 50,000 columns and 300,000 rows.

// main.js
import VXETable from 'vxe-table'
import 'vxe-table/lib/style.css'
Vue.use(VXETable)
<template>
<vxe-table border show-overflow ref="xTable1" height="300" :row-config="{ isHover: true }" :loading="loading">
<vxe-column type="seq"/>
<vxe-column field="id" title="ID"/>
<vxe-column field="name" title="名字"/>
<vxe-column field="value" title="对应值"/>
</vxe-table>
</template>
<script>
export default {
data() { return { loading: false }; },
async created() { this.loading = true; const res = await axios.get("http://ashuai.work:10000/bigData"); this.loading = false; this.$refs.xTable1.loadData(res.data.data); }
};
</script>

All the above methods aim to keep the UI responsive while dealing with massive front‑end data sets, allowing developers to choose the strategy that best fits their project requirements.

frontendPerformanceJavaScriptVueInfinite Scrolllarge-datavirtual-list
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.