# Flying Posters

> 3D posters that twist and flip as you scroll or drag through an endless column.

Scroll the mouse wheel over the posters or drag them up and down. Each poster twists and flips as it crosses the view, and the column loops forever. The canvas only animates while you use it, unless you set a drift `speed`. Add columns, round the corners, or pull the camera back with `camera-z`.

<demo-flying-posters>



</demo-flying-posters>

## Installation

```bash
npx shadcn-vue@latest add "https://nxui.geoql.in/r/flying-posters.json"
```

## Usage

```vue
<script setup lang="ts">
  import FlyingPosters from '~/components/ui/flying-posters/FlyingPosters.vue';
  import type { FlyingPostersItem } from '~/components/ui/flying-posters/types';

  const items = [
    'https://images.unsplash.com/photo-1500382017468-9049fed747ef?w=800',
    'https://images.unsplash.com/photo-1519681393784-d120267933ba?w=800',
    { image: '/posters/city.webp', alt: 'City at night' },
  ];

  function onPosterClick(item: FlyingPostersItem, index: number) {
    console.log(index, item.image);
  }
</script>

<template>
  <div class="relative h-125 w-full">
    <FlyingPosters
      :items="items"
      :columns="3"
      :plane-width="200"
      :plane-height="260"
      :radius="12"
      :speed="30"
      @item-click="onPosterClick"
    />
  </div>
</template>
```

The component fills its parent, so give the parent a height. Remote images must allow CORS, because they are drawn into a WebGL texture.

## 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>
        Array<string | FlyingPostersItem>
      </code>
    </td>
    
    <td>
      <code>
        []
      </code>
    </td>
    
    <td>
      Poster images. Each is cropped to fill its poster; they repeat to fill the view.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        plane-width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        320
      </code>
    </td>
    
    <td>
      Poster width in px at the default camera.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        plane-height
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        320
      </code>
    </td>
    
    <td>
      Poster height in px at the default camera.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        distortion
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        3
      </code>
    </td>
    
    <td>
      How far one corner turns ahead of the other, which gives the twist.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        scroll-ease
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.01
      </code>
    </td>
    
    <td>
      How quickly the posters catch up with wheel and drag. Lower glides longer.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        camera-fov
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        45
      </code>
    </td>
    
    <td>
      Camera field of view in degrees. Wider angles zoom out.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        camera-z
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        20
      </code>
    </td>
    
    <td>
      Camera distance. Higher shows posters smaller and flatter; lower brings them close.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Space between posters in px. Defaults to a spacing that follows the camera.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        columns
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Number of poster columns (1–8).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        column-gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        40
      </code>
    </td>
    
    <td>
      Space between columns in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        alternate
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Moves every other column in the opposite direction.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Corner radius of the posters in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rotation
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        180
      </code>
    </td>
    
    <td>
      How far each poster turns while it crosses the view, in degrees.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        axis
      </code>
    </td>
    
    <td>
      <code>
        'vertical' | 'horizontal' | 'diagonal'
      </code>
    </td>
    
    <td>
      <code>
        'vertical'
      </code>
    </td>
    
    <td>
      Axis the posters turn around.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        speed
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Drift in px per second while idle. Negative values drift down.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        lean
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      How far the posters lean back with fast scrolling (0–2).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fade
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Fades the posters toward the top and bottom edges, 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        shading
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Darkens the posters as they turn away, 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        wheel
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Scrolls the posters with the mouse wheel while hovered.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drag
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Scrolls the posters by dragging them.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        page-scroll
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Moves the posters with the page scroll, as a multiplier. 0 turns it off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the container.
    </td>
  </tr>
</tbody>
</table>

### FlyingPostersItem

<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
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        link
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Optional link to use in your click handler
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        alt
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      Optional description
    </td>
  </tr>
</tbody>
</table>

## Events

<table>
<thead>
  <tr>
    <th>
      Event
    </th>
    
    <th>
      Payload
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        item-click
      </code>
    </td>
    
    <td>
      <code>
        (item: FlyingPostersItem, index: number)
      </code>
    </td>
    
    <td>
      Fired when a poster is clicked or tapped.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- The canvas has `role="img"`. It is decorative motion, so keep important content and links elsewhere on the page too.
- With `prefers-reduced-motion: reduce`, the drift, lean and fade-in are off; the posters move only when you scroll or drag.
- The canvas pauses when it is off screen.
- Ported from [React Bits](https://reactbits.dev/components/flying-posters) by David Haz.
