# Fisheye Infinite Grid

> A dense, draggable image wall that repeats forever on both axes and is bent by one continuous fisheye lens, with weighted momentum.

A WebGL image wall that tiles seamlessly in every direction and bends through one center-weighted fisheye lens. Drag to pan and release to let it coast, scroll with a wheel or trackpad, or focus it and use the arrow keys. A mouse entering the grid gives it a small drift away from the edge you came in from.

<demo-fisheye-infinite-grid>



</demo-fisheye-infinite-grid>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/fisheye-infinite-grid.json"
```

## Usage

```vue
<script setup lang="ts">
  import FisheyeInfiniteGrid from '~/components/ui/fisheye-infinite-grid/FisheyeInfiniteGrid.vue';
  import type { FisheyeGridItem } from '~/components/ui/fisheye-infinite-grid/types';

  const items: FisheyeGridItem[] = [
    {
      image:
        'https://images.unsplash.com/photo-1514906689926-25ba6dcb584b?w=900',
      alt: 'Soft motion-blurred portrait in pale light',
      title: 'Silver Veil',
      meta: '01',
    },
    // …
  ];
</script>

<template>
  <div class="relative h-155 w-full overflow-hidden">
    <FisheyeInfiniteGrid
      :items="items"
      :tile-width="238"
      :tile-height="272"
      :lens-strength="0.24"
      theme="system"
    />
  </div>
</template>
```

All tiles are painted once into a 2D atlas canvas, which a single full-screen
fragment shader samples with `fract()` so the wall never ends. The lens warp,
edge shading and pan offset all happen in that one draw call. Images load with
`crossOrigin="anonymous"`, so their host must send CORS headers.

## Props

<table>
<thead>
  <tr>
    <th>
      Prop
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Default
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        items
      </code>
    </td>
    
    <td>
      <code>
        FisheyeGridItem[]
      </code>
    </td>
    
    <td>
      12 editorial motion photos
    </td>
    
    <td>
      Images and labels repeated across the grid. An empty array uses the default.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tile-width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        238
      </code>
    </td>
    
    <td>
      Width of each tile in CSS pixels (minimum 140).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tile-height
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        272
      </code>
    </td>
    
    <td>
      Height of each tile in CSS pixels (minimum 150).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Space between tiles in CSS pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        lens-strength
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.24
      </code>
    </td>
    
    <td>
      Strength of the center-weighted bend, clamped to 0–2.5. <code>
        0
      </code>
      
       is a flat grid.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        theme
      </code>
    </td>
    
    <td>
      <code>
        'dark' | 'light' | 'system'
      </code>
    </td>
    
    <td>
      <code>
        'system'
      </code>
    </td>
    
    <td>
      Surface, tile chrome and vignette colors. <code>
        system
      </code>
      
       follows the page theme.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hover-nudge
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        16
      </code>
    </td>
    
    <td>
      Distance of the drift shown when a mouse enters the grid. <code>
        0
      </code>
      
       disables it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        inertia
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.94
      </code>
    </td>
    
    <td>
      Momentum kept after a drag release, clamped to 0–0.98.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        wheel-sensitivity
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.42
      </code>
    </td>
    
    <td>
      Wheel and trackpad response. Lower values give a slower, heavier pan.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aria-label
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Infinite draggable image grid'
      </code>
    </td>
    
    <td>
      Accessible name of the grid region.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Extra classes merged onto the root element.
    </td>
  </tr>
</tbody>
</table>

### `FisheyeGridItem`

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Type
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        image
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Image URL drawn inside the tile.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        alt
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Image description. Used as the label fallback.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        title
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Optional primary label, shown in upper case.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        meta
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Optional secondary label, right-aligned.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The root is a focusable `region` with a visible inset focus ring. Arrow keys pan the grid by 42px per press.
- A screen-reader-only hint explains the drag and arrow-key controls. The canvas itself is `aria-hidden`.
- With `prefers-reduced-motion: reduce`, the hover drift, release momentum and wheel coasting are off. Drag and arrow keys still move the grid directly.
- The render loop sleeps when the grid is still and pauses while it is offscreen.
- Ported from [componentry.fun](https://componentry.dev/docs/components/fisheye-infinite-grid) by Harsh Jadhav.
