목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
격자·벽돌형·자유 좌표
배치 방식은 화면이 가진 구조에 맞춰 고릅니다. 각 예제의 코드 탭은 상단 사용 환경 설정을 따릅니다.
트랙 격자
행과 열이 모두 독립적으로 큰 화면에는 트랙 격자를 사용합니다. 아래 예제는 300개 행 × 300개 열, 총 90,000개 셀을 같은 비중으로 만들었습니다. 가로와 세로를 모두 스크롤해도 현재 viewport와 overscan에 들어온 셀만 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>트랙 격자는 행 높이와 열 너비를 각각 관리합니다. 셀이 여러 행이나 열을 차지하는 병합 영역도 같은 좌표계에 둘 수 있습니다. Vue의 VirtualizerRoot에는 trackGridLayoutStrategy를 연결합니다.
상품 카드처럼 화면 너비에 맞춰 열 수가 바뀌고 세로 방향으로만 이어지는 화면에는 VirtualGrid가 더 간단합니다. minLaneSize를 전달하면 viewport 너비에 맞춰 열 수를 계산합니다.
벽돌형
높이가 다른 카드를 빈 공간이 적도록 여러 열에 배치합니다. 아래 예제는 30,000개 카드의 실제 DOM 높이를 측정하고, viewport 주변의 카드만 만듭니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
내용이 여러 줄로 늘어나도 별도의 높이 계산 함수 없이 배치에 반영됩니다.
목록 앞쪽이 바뀌면 현재 읽고 있는 항목을 기준으로 스크롤 위치를 보정합니다.
화면에 들어온 실제 콘텐츠의 높이를 측정해 다음 항목의 위치를 갱신합니다.
<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는 화면 너비와 minLaneSize로 열 수를 정합니다. 각 카드의 높이가 측정되면 영향을 받는 뒤쪽 카드만 다시 배치합니다.
자유 좌표
다이어그램과 편집기처럼 앱이 각 항목의 x·y 좌표와 크기를 이미 가지고 있을 때 사용합니다. 아래 예제는 40,000개의 서비스 노드를 불규칙한 군집으로 배치했습니다. 행·열 규칙이나 고정된 노드 크기 없이, 가로와 세로 어느 방향으로 이동해도 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>VirtualSpatial에는 getRect로 x, y, 너비, 높이를 전달합니다. Sectile은 앱의 좌표를 유지한 채 현재 화면과 겹치는 항목을 빠르게 찾습니다.
선택 기준
| 자료가 가진 구조 | 배치 |
|---|---|
| 순서와 항목 크기 | 선형 |
| 독립적인 행과 열 | 트랙 격자 |
| 순서와 서로 다른 카드 높이 | 벽돌형 |
| 이미 정해진 x·y 사각형 | 자유 좌표 |
