# Pixel Swap

> New content builds in pixel by pixel in a springy wave that starts wherever the cursor enters.

Swaps between two pieces of content with a springy wave of pixels. Hover the card and the second image builds in from the point where the cursor enters; move away and it retreats from where you left. Switch `trigger` to `click` to toggle on click, or drive it yourself with `v-model:active`.

<demo-pixel-swap>



</demo-pixel-swap>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import PixelSwap from '~/components/ui/pixel-swap/PixelSwap.vue';
</script>

<template>
  <PixelSwap
    class="max-w-140 rounded-2xl"
    pattern="pointer"
    :pixel-size="48"
    :duration="400"
    :pixel-duration="350"
    :randomness="0.2"
    :bounce="0.3"
    trigger="hover"
    intro
  >
    <template #first>
      <img src="/day-sky.webp" alt="" class="size-full object-cover" />
    </template>
    <template #second>
      <img src="/night-sky.webp" alt="" class="size-full object-cover" />
    </template>
  </PixelSwap>
</template>
```

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        pattern
      </code>
    </td>
    
    <td>
      <code>
        'pointer' | 'random' | 'center' | 'edges' | 'left-to-right' | 'right-to-left' | 'top-to-bottom' | 'bottom-to-top' | 'diagonal' | 'spiral'
      </code>
    </td>
    
    <td>
      <code>
        'pointer'
      </code>
    </td>
    
    <td>
      Order the pixels arrive in. Pointer starts the wave where the cursor enters, leaves or clicks.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pixel-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        48
      </code>
    </td>
    
    <td>
      Edge length of each pixel in px. Grows automatically when the grid would pass 600 pixels.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        gap
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Space between pixels in px while they grow.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pixel-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Corner rounding of the pixels while they grow, as a percentage. 50 starts them as dots.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pixel-scale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Size each pixel pops in at, from 0 to grow out of nothing to 0.9 for a hard pop.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pixel-spin
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Degrees each pixel turns as it grows in.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      Time in ms the wave takes to cross the content.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pixel-duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        350
      </code>
    </td>
    
    <td>
      Time in ms a single pixel takes to grow.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        randomness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.2
      </code>
    </td>
    
    <td>
      Noise mixed into the pattern, from 0 for a clean wave to 1 for a scatter.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        bounce
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.3
      </code>
    </td>
    
    <td>
      How far each pixel overshoots before it settles, from 0 to 1.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        trigger
      </code>
    </td>
    
    <td>
      <code>
        'hover' | 'click' | 'none'
      </code>
    </td>
    
    <td>
      <code>
        'hover'
      </code>
    </td>
    
    <td>
      What starts the swap. With <code>
        none
      </code>
      
      , drive it with <code>
        active
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        intro
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Builds the first content in from pixels on mount.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        initial-active
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Starts on the second content.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        active
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        undefined
      </code>
    </td>
    
    <td>
      Controls which content shows. Use <code>
        v-model:active
      </code>
      
      , or pair it with <code>
        update:active
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        aspect-ratio
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '16 / 10'
      </code>
    </td>
    
    <td>
      Aspect ratio of the root.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Extra classes for the root element.
    </td>
  </tr>
</tbody>
</table>

## Slots

<table>
<thead>
  <tr>
    <th>
      Slot
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        first
      </code>
    </td>
    
    <td>
      Content shown at rest.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        second
      </code>
    </td>
    
    <td>
      Content that builds in pixel by pixel.
    </td>
  </tr>
</tbody>
</table>

## Events

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

<tbody>
  <tr>
    <td>
      <code>
        update:active
      </code>
    </td>
    
    <td>
      <code>
        (active: boolean)
      </code>
    </td>
    
    <td>
      Fired when the trigger asks to swap. In controlled mode nothing moves until <code>
        active
      </code>
      
       changes.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        complete
      </code>
    </td>
    
    <td>
      <code>
        (active: boolean)
      </code>
    </td>
    
    <td>
      Fired when a swap finishes.
    </td>
  </tr>
</tbody>
</table>

## Accessibility

- With `trigger="click"` the root is a focusable button (`role="button"`, `tabindex="0"`, `aria-pressed`) that responds to Enter and Space.
- With `trigger="hover"` the root is focusable: keyboard focus shows the second content and blur shows the first. A tap toggles it on touch screens.
- The root shows a visible focus ring (`outline: 2px solid currentColor`).
- With `prefers-reduced-motion: reduce` the swap is skipped and content switches instantly.
- Each layer is hidden with `visibility: hidden` when inactive and marked `aria-hidden`.
- Ported from [React Bits](https://reactbits.dev/animations/pixel-swap) by David Haz.
