# Masked Heading

> A headline with an image or looping video showing through the letters, revealed by rise, scale, blur, wipe or fade, with pointer parallax and an optional spotlight.

The letters of a heading become a window onto an image or a muted looping video. The media drifts slowly and slides under the letters as the pointer moves; turn on `spotlight` to dim everything except a soft pool of light that follows the pointer. The heading enters with a word rise, letter-by-letter scale, blur, wipe or fade — when it scrolls into view, on mount, or on hover.

<demo-masked-heading>



</demo-masked-heading>

## Installation

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

## Usage

```vue
<script setup lang="ts">
  import MaskedHeading from '~/components/ui/masked-heading/MaskedHeading.vue';
</script>

<template>
  <MaskedHeading
    tag="h1"
    text="Shot on location"
    src="https://images.unsplash.com/photo-1500673587002-1d2548cfba1b?w=1600"
    reveal="rise"
    spotlight
    :spotlight-radius="200"
    :hover-zoom="0.06"
  />
</template>
```

The visible letters are a real heading element (transparent text keeps it
selectable and readable by assistive tech); an SVG `clipPath` built from each
measured glyph cuts the media to the letter shapes, and the entrance animates
those glyphs.

## Props

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

<tbody>
  <tr>
    <td>
      <code>
        text
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'Designed in the details'
      </code>
    </td>
    
    <td>
      Heading copy
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tag
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        'h2'
      </code>
    </td>
    
    <td>
      Element the heading renders as
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        media-type
      </code>
    </td>
    
    <td>
      <code>
        'image' | 'video'
      </code>
    </td>
    
    <td>
      <code>
        'image'
      </code>
    </td>
    
    <td>
      Image, or a muted looping video
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        src
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Image or video URL
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        poster
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Poster frame used while a video loads
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reveal
      </code>
    </td>
    
    <td>
      <code>
        'rise' | 'scale' | 'blur' | 'wipe' | 'fade' | 'none'
      </code>
    </td>
    
    <td>
      <code>
        'rise'
      </code>
    </td>
    
    <td>
      Entrance style
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        trigger
      </code>
    </td>
    
    <td>
      <code>
        'view' | 'mount' | 'hover'
      </code>
    </td>
    
    <td>
      <code>
        'view'
      </code>
    </td>
    
    <td>
      When the entrance runs
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        duration
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.1
      </code>
    </td>
    
    <td>
      Entrance duration, in seconds
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stagger
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.09
      </code>
    </td>
    
    <td>
      Delay between words (rise); letters step at about a third (scale)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        spotlight
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Dims the media except for a pool of light following the pointer
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        spotlight-radius
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        160
      </code>
    </td>
    
    <td>
      Spotlight radius, in px
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        outline
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Strokes every letter, visible before the entrance fills it
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        outline-color
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Outline color; empty follows the text color
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        outline-width
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Outline thickness, in px
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        fill-scale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.25
      </code>
    </td>
    
    <td>
      How far the media is zoomed past the heading (parallax overscan)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        parallax
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        26
      </code>
    </td>
    
    <td>
      How far the media slides with the pointer, in px
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        drift
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        18
      </code>
    </td>
    
    <td>
      Amplitude of the slow idle motion, in px; <code>
        0
      </code>
      
       holds still
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        hover-zoom
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0
      </code>
    </td>
    
    <td>
      Extra zoom while hovered (<code>
        0.1
      </code>
      
       = 10% closer)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        brightness
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Brightness of the media
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        saturation
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1
      </code>
    </td>
    
    <td>
      Saturation of the media
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        grayscale
      </code>
    </td>
    
    <td>
      <code>
        boolean
      </code>
    </td>
    
    <td>
      <code>
        false
      </code>
    </td>
    
    <td>
      Render the media in black and white
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tint
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      <code>
        ''
      </code>
    </td>
    
    <td>
      Colorizes the media in one color, like a duotone print
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        align
      </code>
    </td>
    
    <td>
      <code>
        'left' | 'center' | 'right'
      </code>
    </td>
    
    <td>
      <code>
        'center'
      </code>
    </td>
    
    <td>
      Text alignment
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        weight
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        700
      </code>
    </td>
    
    <td>
      Font weight
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tracking
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        -0.03
      </code>
    </td>
    
    <td>
      Letter spacing, in em
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        line-height
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        1.06
      </code>
    </td>
    
    <td>
      Line height
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        text-scale
      </code>
    </td>
    
    <td>
      <code>
        number
      </code>
    </td>
    
    <td>
      <code>
        0.115
      </code>
    </td>
    
    <td>
      Type size as a fraction of the container width
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        class
      </code>
    </td>
    
    <td>
      <code>
        string
      </code>
    </td>
    
    <td>
      —
    </td>
    
    <td>
      Additional classes on the root element
    </td>
  </tr>
</tbody>
</table>

Other attributes (`id`, `aria-*`) pass through to the heading element.

## Accessibility

- Renders a real heading (`tag`), so document outline and screen readers get the text; the media and SVG layers are `aria-hidden` / decorative.
- With `prefers-reduced-motion: reduce` the entrance is skipped, drift and parallax stop and videos stay paused.
- The idle loop pauses while the heading is off screen.
- Ported from [React Bits](https://reactbits.dev/text-animations/masked-heading) by David Haz.
