Skip to main content
A high-performance, interactive 3D infinite grid component built with OGL as seen on phantom.land.

Installation

npm install ogl gsap

Usage

<script setup lang="ts">
import { InfiniteGrid } from '@/components/visualization'
import type { CardData } from '@/components/visualization'

const cardData: CardData[] = [
  {
    title: 'Project 1',
    badge: 'New',
    description: 'Project description',
    tags: ['Vue', 'TypeScript'],
    date: '2024-01-01',
    image: '/images/project1.jpg',
  },
]

const options = {
  gridCols: 4,
  gridRows: 4,
  gridGap: 0,
  tileSize: 2.4,
  baseCameraZ: 10,
}

function handleTileClick({ card, index }) {
  console.log('Clicked:', card, index)
}
</script>

<template>
  <InfiniteGrid 
    :card-data="cardData" 
    :options="options"
    @tile-clicked="handleTileClick"
  />
</template>

Props

Prop NameTypeDefaultDescription
cardDataCardData[][]Data for every tile shown in the grid. Required.
optionsPartial<InfiniteGridOptions>{}Optional overrides for layout, camera, and post-processing (see table below).

InfiniteGridOptions

OptionTypeDefaultDescription
gridColsnumber4Grid Columns .
gridRowsnumber4Grid Rows .
gridGapnumber0Gap between squares.
tileSizenumber2.4Tile size (OGL units).
baseCameraZnumber10Starting Z-distance of the camera.
enablePostProcessingbooleantrueToggle the post-processing pipeline.
postProcessParams.distortionIntensitynumber-0.2Barrel / pincushion distortion strength (0 = none).
postProcessParams.vignetteOffsetnumber0.0Vignette offset; higher ⇒ smaller clear area.
postProcessParams.vignetteDarknessnumber0.0Vignette darkness; higher ⇒ darker edges.

CardData

FieldTypeRequiredDescription
titlestringMain heading text.
badgestringBadge label (render logic can be customised).
descriptionstringLonger body text.
tagsstring[]Tag pills rendered at the bottom.
datestringDate string shown bottom-right.
imagestringURL for the tile’s background image.

Events

Event NamePayloadDescription
tileClicked{ card: CardData, index: number }Fired whenever a tile is clicked/tapped. The payload contains the clicked CardData object and its zero-based index.
onTileLoaded-Fired when all the images are loaded inside the tile.

Credits

  • Inspired from Phantom Land
  • Special thanks to Safak Dinc for the idea and for granting permission to include it here. You can find the original repository at infinite-grid
  • Credits to kalix127 for porting this component

Build docs developers (and LLMs) love