Photo Gallery
Lazy-loaded image grid with row-based virtualization
A high-performance grid gallery displaying 2,000 photos. Features lazy-loading placeholders and a lightbox.
Grid Columns5
Photographer 0
Photographer 1
Photographer 2
Photographer 3
Photographer 4
Photographer 5
Photographer 6
Photographer 7
Photographer 8
Photographer 9
Photographer 10
Photographer 11
Photographer 12
Photographer 13
Photographer 14
Photographer 15
Photographer 16
Photographer 17
Photographer 18
Photographer 19
Photographer 20
Photographer 21
Photographer 22
Photographer 23
Photographer 24
<script setup lang="ts">
import type { Ref } from 'vue';
import { VirtualScroll } from '@pdanpdan/virtual-scroll';
import { computed, inject, ref } from 'vue';
import ExampleContainer from '#/components/ExampleContainer.vue';
import ScrollStatus from '#/components/ScrollStatus.vue';
import { useExampleScroll } from '#/lib/useExampleScroll';
import { html as highlightedCode } from './+Page.vue?highlight';
interface Photo {
id: number;
thumb: string;
full: string;
author: string;
}
const debugMode = inject<Ref<boolean>>('debugMode', ref(false));
const itemCount = ref(2000);
const columns = ref(5);
const photos = computed(() => Array.from(
{ length: Math.ceil(itemCount.value / columns.value) },
(_, rowIdx) => Array.from({ length: columns.value }, (_, colIdx) => {
const id = rowIdx * columns.value + colIdx;
return {
id,
thumb: `https://picsum.photos/seed/${ id + 1 }/400/400`,
full: `https://picsum.photos/seed/${ id + 1 }/1200/800`,
author: `Photographer ${ id }`,
} as Photo;
}),
));
const {
scrollDetails,
onScroll,
} = useExampleScroll();
</script>
<template>
<ExampleContainer :code="highlightedCode">
<template #title>
<span class="example-title example-title--group-6">Photo Gallery</span>
</template>
<template #description>
A high-performance grid gallery displaying {{ itemCount.toLocaleString() }} photos. Features lazy-loading placeholders and a lightbox.
</template>
<template #icon>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="example-icon example-icon--group-6"
>
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg>
</template>
<template #subtitle>
Lazy-loaded image grid with row-based virtualization
</template>
<template #controls>
<ScrollStatus :scroll-details="scrollDetails" />
</template>
<template #example-controls>
<div class="flex flex-wrap gap-4 items-center">
<div class="flex flex-col gap-1">
<span class="flex justify-between items-center">
<span class="text-xs font-bold opacity-50 small-caps tracking-wider">Grid Columns</span>
<span class="badge badge-sm badge-primary font-mono">{{ columns }}</span>
</span>
<input
v-model.number="columns"
type="range"
min="1"
max="8"
step="1"
class="range range-xs range-primary w-48"
aria-label="Grid Columns"
/>
</div>
</div>
</template>
<VirtualScroll
class="example-container p-4"
:items="photos"
:gap="16"
:debug="debugMode"
aria-label="Photo gallery"
@scroll="onScroll"
>
<template #item="{ index: rowIndex, item: rowItems }">
<div
:key="`r_${ rowIndex }`"
class="grid gap-4 w-full"
:style="{ gridTemplateColumns: `repeat(${ columns }, 1fr)` }"
>
<div
v-for="(photo, colIndex) in rowItems"
:key="`r_${ rowIndex }_c_${ colIndex }`"
class="rounded-box overflow-hidden relative outline-none border border-base-content/5 focus-visible:ring-2 focus-visible:ring-primary transition-transform active:scale-95 group aspect-square bg-base-200"
>
<img
:src="photo.thumb"
:alt="`Photo by ${ photo.author }`"
class="size-full object-cover transition-transform duration-500 group-hover:scale-110"
loading="lazy"
/>
<div class="absolute inset-0 bg-linear-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity flex items-end p-2 @4xl:p-4">
<span class="text-white text-xs @4xl:text-sm font-medium truncate">{{ photo.author }}</span>
</div>
</div>
</div>
</template>
</VirtualScroll>
</ExampleContainer>
</template>
- Scroll Status
- Directionvertical
- Current Item #-
- Rendered Range #0:0
- Total Size (px)0w ×0h
- Viewport Size (px)0w ×0h
- Scroll Offset (px)0x ×0y