# Dither Veil

> A 1-bit dithered photo the cursor burns through to full colour, knitting back cell by cell.

A dithered image the cursor burns through to full colour. Move the pointer over the photo and a full-colour window follows it, knitting back into dither cell by cell as it passes. Click to send a ring of colour rippling out across the image.

<demo-dither-veil>



</demo-dither-veil>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import DitherVeil from '~/components/ui/dither-veil/DitherVeil.vue';
</script>

<template>
  <div class="relative h-125 w-full overflow-hidden">
    <DitherVeil
      pattern="floyd"
      :pixel-size="2"
      :levels="2"
      ink-color="#120f17"
      paper-color="#f4f1ea"
      :contrast="1.15"
      :reveal-radius="200"
    />
  </div>
</template>
```

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        src
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'https://images.unsplash.com/...'
      </code>
    </td>
    
    <td>
      Image to veil. Must be served with CORS headers.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fit
      </code>
    </td>
    
    <td>
      <code>
        'contain' | 'cover'
      </code>
    </td>
    
    <td>
      <code>
        'contain'
      </code>
    </td>
    
    <td>
      Show the whole image on the ink, or crop it to fill the container.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pattern
      </code>
    </td>
    
    <td>
      <code>
        'bayer' | 'noise' | 'atkinson' | 'floyd' | 'lines'
      </code>
    </td>
    
    <td>
      <code>
        'floyd'
      </code>
    </td>
    
    <td>
      How the image is broken into dots: an ordered Bayer grid, blue noise stipple, Atkinson or Floyd-Steinberg error diffusion, or diagonal engraving lines.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        palette
      </code>
    </td>
    
    <td>
      <code>
        'duotone' | 'rgb'
      </code>
    </td>
    
    <td>
      <code>
        'duotone'
      </code>
    </td>
    
    <td>
      Duotone maps brightness between the ink and paper colours. RGB dithers each channel for a retro colour look.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pixel-size
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      Size of each dither cell, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        levels
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        2
      </code>
    </td>
    
    <td>
      Tones per channel. 2 is pure 1-bit, higher values add in-between shades.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ink-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#120f17'
      </code>
    </td>
    
    <td>
      Colour of the darkest tone.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        paper-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#f4f1ea'
      </code>
    </td>
    
    <td>
      Colour of the lightest tone.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        contrast
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.15
      </code>
    </td>
    
    <td>
      Tonal contrast applied before dithering.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        brightness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Shifts the image lighter or darker before dithering.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reveal-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        200
      </code>
    </td>
    
    <td>
      Radius of the full-colour window around the cursor, in px.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        softness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.6
      </code>
    </td>
    
    <td>
      How much of the reveal edge dissolves through the dither. 0 is a hard cut.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        linger
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Seconds the revealed trail takes to knit back into dither. 0 turns the trail off.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rim-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        '#a78bfa'
      </code>
    </td>
    
    <td>
      Colour of the cells along the dissolving edge.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rim
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Thickness of the coloured rim on the dissolving edge. 0 hides it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reverse
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Start in full colour and dither wherever the cursor goes.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        wander
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Let the reveal drift around on its own while the pointer is away.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        click-burst
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
    </td>
    
    <td>
      Clicking sends a ring of colour rippling out across the image.
    </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>

## Accessibility

- The canvas is decorative. Add a visible caption or an `aria-label` on a parent if the image carries meaning.
- With `prefers-reduced-motion: reduce` the reveal still follows the cursor, but `wander` is ignored so nothing moves on its own.
- The render loop stops while the component is offscreen or idle, and the WebGL context is released on unmount.
- Needs WebGL 2. Without it the component renders an empty container.
- Ported from [React Bits](https://reactbits.dev/animations/dither-veil) by David Haz.
