Virtual scrolling helps show only a small part of a big list on screen. This makes the app faster and smoother.
Virtual scrolling for large lists in Vue
<template> <div ref="scrollContainer" @scroll="onScroll" style="height: 300px; overflow-y: auto;"> <div :style="{ height: totalHeight + 'px', position: 'relative' }"> <div v-for="item in visibleItems" :key="item.id" :style="{ position: 'absolute', top: itemPositions[item.index] + 'px', height: itemHeight + 'px', width: '100%' }" > {{ item.text }} </div> </div> </div> </template> <script setup> import { ref, computed, onMounted } from 'vue' const props = defineProps({ items: Array, itemHeight: Number }) const scrollTop = ref(0) const scrollContainer = ref(null) const visibleCount = computed(() => Math.ceil(300 / props.itemHeight) + 1) const totalHeight = computed(() => props.items.length * props.itemHeight) const startIndex = computed(() => Math.floor(scrollTop.value / props.itemHeight)) const visibleItems = computed(() => { return props.items.slice(startIndex.value, startIndex.value + visibleCount.value).map((item, index) => ({ ...item, index: startIndex.value + index })) }) const itemPositions = computed(() => { const positions = {} for (let i = startIndex.value; i < startIndex.value + visibleCount.value; i++) { positions[i] = i * props.itemHeight } return positions }) function onScroll() { scrollTop.value = scrollContainer.value.scrollTop } onMounted(() => { scrollTop.value = 0 }) </script>
The container must have a fixed height and overflow-y set to auto or scroll.
Each item is absolutely positioned inside a tall container to simulate full list height.
<template> <div ref="scrollContainer" @scroll="onScroll" style="height: 300px; overflow-y: auto;"> <div :style="{ height: totalHeight + 'px', position: 'relative' }"> <div v-for="item in visibleItems" :key="item.id" :style="{ position: 'absolute', top: itemPositions[item.index] + 'px', height: itemHeight + 'px', width: '100%' }" > {{ item.text }} </div> </div> </div> </template> <script setup> import { ref, computed } from 'vue' const items = ref([]) // empty list const itemHeight = 30 const scrollTop = ref(0) const scrollContainer = ref(null) const visibleCount = computed(() => Math.ceil(300 / itemHeight) + 1) const totalHeight = computed(() => items.value.length * itemHeight) const startIndex = computed(() => Math.floor(scrollTop.value / itemHeight)) const visibleItems = computed(() => { return items.value.slice(startIndex.value, startIndex.value + visibleCount.value).map((item, index) => ({ ...item, index: startIndex.value + index })) }) const itemPositions = computed(() => { const positions = {} for (let i = startIndex.value; i < startIndex.value + visibleCount.value; i++) { positions[i] = i * itemHeight } return positions }) function onScroll() { scrollTop.value = scrollContainer.value.scrollTop } </script>
<template> <div ref="scrollContainer" @scroll="onScroll" style="height: 300px; overflow-y: auto;"> <div :style="{ height: totalHeight + 'px', position: 'relative' }"> <div v-for="item in visibleItems" :key="item.id" :style="{ position: 'absolute', top: itemPositions[item.index] + 'px', height: itemHeight + 'px', width: '100%' }" > {{ item.text }} </div> </div> </div> </template> <script setup> import { ref, computed } from 'vue' const items = ref([{ id: 1, text: 'Only one item' }]) const itemHeight = 30 const scrollTop = ref(0) const scrollContainer = ref(null) const visibleCount = computed(() => Math.ceil(300 / itemHeight) + 1) const totalHeight = computed(() => items.value.length * itemHeight) const startIndex = computed(() => Math.floor(scrollTop.value / itemHeight)) const visibleItems = computed(() => { return items.value.slice(startIndex.value, startIndex.value + visibleCount.value).map((item, index) => ({ ...item, index: startIndex.value + index })) }) const itemPositions = computed(() => { const positions = {} for (let i = startIndex.value; i < startIndex.value + visibleCount.value; i++) { positions[i] = i * itemHeight } return positions }) function onScroll() { scrollTop.value = scrollContainer.value.scrollTop } </script>
<template> <div ref="scrollContainer" @scroll="onScroll" style="height: 300px; overflow-y: auto;"> <div :style="{ height: totalHeight + 'px', position: 'relative' }"> <div v-for="item in visibleItems" :key="item.id" :style="{ position: 'absolute', top: itemPositions[item.index] + 'px', height: itemHeight + 'px', width: '100%' }" > {{ item.text }} </div> </div> </div> </template> <script setup> import { ref, computed } from 'vue' const items = ref(Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, text: `Item ${i + 1}` }))) const itemHeight = 30 const scrollTop = ref(0) const scrollContainer = ref(null) const visibleCount = computed(() => Math.ceil(300 / itemHeight) + 1) const totalHeight = computed(() => items.value.length * itemHeight) const startIndex = computed(() => Math.floor(scrollTop.value / itemHeight)) const visibleItems = computed(() => { return items.value.slice(startIndex.value, startIndex.value + visibleCount.value).map((item, index) => ({ ...item, index: startIndex.value + index })) }) const itemPositions = computed(() => { const positions = {} for (let i = startIndex.value; i < startIndex.value + visibleCount.value; i++) { positions[i] = i * itemHeight } return positions }) function onScroll() { scrollTop.value = scrollContainer.value.scrollTop } </script>
This Vue component shows a list of 1000 items using virtual scrolling. Only the items visible in the 300px tall container are rendered. Each item is 40px tall and positioned absolutely inside a tall container. Scrolling updates which items are visible.
Keyboard navigation is supported by adding tabindex and role attributes for accessibility.
<template>
<div>
<h2>Virtual Scrolling List</h2>
<div ref="scrollContainer" @scroll="onScroll" style="height: 300px; overflow-y: auto; border: 1px solid #ccc;">
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
<div
v-for="item in visibleItems"
:key="item.id"
:style="{ position: 'absolute', top: itemPositions[item.index] + 'px', height: itemHeight + 'px', width: '100%', padding: '0.5rem', boxSizing: 'border-box', borderBottom: '1px solid #eee', backgroundColor: item.index % 2 === 0 ? '#fafafa' : '#fff' }"
tabindex="0"
role="listitem"
aria-label="List item"
>
{{ item.text }}
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const itemHeight = 40
const items = ref(Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, text: `Item number ${i + 1}` })))
const scrollTop = ref(0)
const scrollContainer = ref(null)
const visibleCount = computed(() => Math.ceil(300 / itemHeight) + 1)
const totalHeight = computed(() => items.value.length * itemHeight)
const startIndex = computed(() => Math.floor(scrollTop.value / itemHeight))
const visibleItems = computed(() => {
return items.value.slice(startIndex.value, startIndex.value + visibleCount.value).map((item, index) => ({
...item,
index: startIndex.value + index
}))
})
const itemPositions = computed(() => {
const positions = {}
for (let i = startIndex.value; i < startIndex.value + visibleCount.value; i++) {
positions[i] = i * itemHeight
}
return positions
})
function onScroll() {
scrollTop.value = scrollContainer.value.scrollTop
}
onMounted(() => {
scrollTop.value = 0
})
</script>Virtual scrolling reduces rendering work by only showing visible items, improving speed.
Time complexity for rendering is O(visibleCount), not total items.
Common mistake: forgetting to set container height or overflow, which breaks scrolling.
Use virtual scrolling when lists are very large; for small lists normal rendering is simpler.
Virtual scrolling shows only visible items in a large list to improve performance.
It uses a fixed height container and absolute positioning inside a tall wrapper.
Scroll events update which items are rendered dynamically.
Practice
Solution
Step 1: Understand virtual scrolling concept
Virtual scrolling renders only the visible portion of a large list instead of all items at once.Step 2: Identify the benefit in Vue apps
This reduces rendering load and improves performance when dealing with many items.Final Answer:
To render only visible items to improve performance -> Option BQuick Check:
Virtual scrolling = Render visible items only [OK]
- Thinking it loads all items at once
- Believing it disables scrolling
- Assuming it sorts items automatically
Solution
Step 1: Identify container style for virtual scrolling
The container must have a fixed height and vertical scrolling enabled (overflow-y: auto).Step 2: Match the correct style snippet
<div style="height: 300px; overflow-y: auto;"></div> sets height to 300px and overflow-y to auto, enabling vertical scroll inside fixed height.Final Answer:
<div style="height: 300px; overflow-y: auto;"></div> -> Option AQuick Check:
Fixed height + vertical scroll = <div style="height: 300px; overflow-y: auto;"></div> [OK]
- Using overflow-x instead of overflow-y
- Setting height to auto instead of fixed
- Using position fixed incorrectly
scrollTop is 200, itemHeight is 50, and visibleCount is 10?const startIndex = Math.floor(scrollTop / itemHeight); const endIndex = startIndex + visibleCount; const visibleItems = items.slice(startIndex, endIndex);
Solution
Step 1: Calculate startIndex from scrollTop and itemHeight
startIndex = floor(200 / 50) = 4.Step 2: Determine endIndex using visibleCount (assume 10)
endIndex = 4 + 10 = 14, so slice is items[4..13].Final Answer:
Items from index 4 to 13 -> Option AQuick Check:
startIndex = 4, endIndex = 14 slice = 4 to 13 [OK]
- Using scrollTop directly as index
- Confusing endIndex with startIndex
- Ignoring floor function
const startIndex = Math.ceil(scrollTop / itemHeight); const endIndex = startIndex + visibleCount; const visibleItems = items.slice(startIndex, endIndex);
Solution
Step 1: Analyze use of Math.ceil for startIndex
Math.ceil rounds up, so if scrollTop/itemHeight is exactly 4, startIndex becomes 4, but if 3.1 it becomes 4, skipping some items.Step 2: Understand impact on visible items
This causes the first partially visible item to be skipped, leading to missing items during scroll.Final Answer:
Using Math.ceil causes skipping the first visible item -> Option DQuick Check:
Math.ceil skips partially visible items [OK]
- Confusing slice with splice
- Ignoring reactive state importance
- Miscalculating endIndex
Solution
Step 1: Understand virtual scrolling requirements
Virtual scrolling needs a fixed height container with scroll enabled and dynamic rendering of visible items.Step 2: Identify correct rendering technique
Absolutely positioning items inside a tall wrapper simulates full list height while rendering only visible items improves performance.Step 3: Evaluate other options
Rendering all items or ignoring scroll position causes lag; dynamic height container breaks scroll behavior.Final Answer:
Use a fixed height container with overflow-y:auto, calculate visible items on scroll, and absolutely position items inside a tall wrapper -> Option CQuick Check:
Fixed container + dynamic visible items + absolute positioning = Use a fixed height container with overflow-y:auto, calculate visible items on scroll, and absolutely position items inside a tall wrapper [OK]
- Rendering all items causing slow UI
- Ignoring scroll position for rendering
- Using dynamic height container breaking scroll
Ready to prove it?
Pass the test and this pattern moves unknown
