Changes before the viewport preserve the item currently being read.
Grid, masonry, and spatial layouts
Choose the layout that matches the structure already present in the surface. Every code tab follows the Usage environment selected in the top navigation.
Track grid
Use a track grid when rows and columns are both independently large. This example contains 300 rows × 300 columns, or 90,000 cells with equal axis counts. Scrolling in either direction keeps only the cells inside the viewport and overscan in the DOM.
<script setup lang="ts">
import { createUniformExtentIndex } from '@sectile/virtual/extent-index'
import { createDenseTrackGridLayout, trackGridLayoutStrategy } from '@sectile/virtual/track-grid-layout'
import { VirtualizerContent, VirtualizerItem, VirtualizerRoot } from '@sectile/vue/virtual/core'
const count = 300
const ids = Array.from({ length: count * count }, (_, index) =>
`cell-${Math.floor(index / count)}-${index % count}`,
)
const grid = createDenseTrackGridLayout(
createUniformExtentIndex(count, { kind: 'exact', value: 28 }),
createUniformExtentIndex(count, { kind: 'exact', value: 72 }),
ids,
)
</script>
<template>
<VirtualizerRoot
:default-state="grid"
:strategy="trackGridLayoutStrategy"
>
<template v-slot="{ placements }">
<VirtualizerContent>
<VirtualizerItem
v-for="cell in placements"
:key="cell.id"
:placement="cell"
size="both"
>
{{ cell.id }}
</VirtualizerItem>
</VirtualizerContent>
</template>
</VirtualizerRoot>
</template>A track grid manages row heights and column widths separately. Spanning regions can occupy multiple tracks in the same coordinate system. In Vue, connect trackGridLayoutStrategy to VirtualizerRoot.
For a product-card grid that only flows vertically and changes its column count with viewport width, VirtualGrid is the smaller API. minLaneSize derives its responsive column count.
Masonry
Masonry places variable-height cards across lanes while reducing empty space. This example measures the actual DOM height of 30,000 cards and creates only those around the viewport.
Measured content updates the position of every item that follows it.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
Content can wrap across lines without an application-owned height calculator.
Changes before the viewport preserve the item currently being read.
Measured content updates the position of every item that follows it.
<script setup lang="ts">
import { VirtualMasonry } from '@sectile/vue/virtual/masonry'
const cards = Array.from({ length: 30_000 }, (_, index) => ({
id: `card-${index}`,
lines: 1 + (index % 5),
}))
</script>
<template>
<VirtualMasonry
:items="cards"
:get-key="card => card.id"
:min-lane-size="104"
:lane-gap="8"
:item-gap="8"
>
<template v-slot="{ value: card }">
<article>
<h3>{{ card.id }}</h3>
<p v-for="lineIndex in card.lines" :key="lineIndex">
Content
</p>
</article>
</template>
</VirtualMasonry>
</template>VirtualMasonry derives its lane count from viewport width and minLaneSize. Once a card is measured, only the affected tail of the layout is updated.
Spatial
Use spatial layout when a diagram or editor already owns each item's x-y coordinates and size. This example arranges 40,000 service nodes in irregular clusters. With no row-column rule or fixed node size, moving in either direction queries only nodes intersecting the viewport.
<script setup lang="ts">
import { VirtualSpatial } from '@sectile/vue/virtual/spatial'
const nodes = Array.from({ length: 40_000 }, (_, index) => {
const cluster = Math.floor(index / 180)
const local = index % 180
const angle = local * 2.4
const radius = Math.sqrt(local) * 26
return {
id: `service-${index}`,
x: 480 + (cluster % 15) * 1_000 + Math.cos(angle) * radius,
y: 450 + Math.floor(cluster / 15) * 800 + Math.sin(angle) * radius,
width: 72 + (index % 4) * 12,
height: 40 + (index % 3) * 8,
layer: local === 0 ? 2 : 0,
}
})
</script>
<template>
<VirtualSpatial
:items="nodes"
:get-key="node => node.id"
:get-rect="node => node"
:get-z-index="node => node.layer"
:measure-size="false"
>
<template v-slot="{ value: node }">
{{ node.id }}
</template>
</VirtualSpatial>
</template>Pass x, y, width, and height through getRect. Sectile keeps those coordinates and performs the viewport intersection query.
Choose by data shape
| Structure already present in the data | Layout |
|---|---|
| Order and item size | Linear |
| Independent rows and columns | Track grid |
| Order and variable card heights | Masonry |
| Existing x-y rectangles | Spatial |
