Scratch to Reveal
Scratch to Reveal paints a gradient over its slot and then erases that paint where the pointer moves, using the canvas destination-out composite. Scratching is a real stroke rather than a trail of dots, so a fast drag still leaves a continuous line.
The component measures how much of the cover is gone when the pointer lifts, and once that share passes threshold it fades the whole cover out and emits complete. Nothing else is animated, which is why the reveal stays crisp while you drag.
Usage
You won
20% off
Code SCRATCH20
Drag across the card to scratch it off.
<template>
<MScratchToReveal
:width="320"
:height="200"
:threshold="45"
class="border border-default"
@complete="revealed = true"
>
<div class="flex flex-col items-center gap-1 text-center">
<p class="text-xs uppercase tracking-widest text-muted">
You won
</p>
<p class="text-3xl font-bold text-highlighted">
20% off
</p>
<p class="text-xs text-muted">
Code SCRATCH20
</p>
</div>
</MScratchToReveal>
</template>
Examples
Customize
You won
20% off
Code SCRATCH20
Customize
Props
| Prop | Type | Default | Description |
|---|---|---|---|
width | number | undefined | Width of the frame, in pixels. Left out, the frame fills its parent and the slot decides the height. |
height | number | undefined | Height of the frame, in pixels. Left out, the slot's content sets it. |
brushSize | number | 24 | Radius of the scratch stroke, in pixels. It stays the same size at any display scale. |
threshold | number | 50 | Share of the cover, from 0 to 100, that has to be gone before the rest fades away. |
gradientColors | string[] | ['#a97cf8', '#f38cb8', '#fdcc92'] | Colours the cover fades through, corner to corner. |
class | string | '' | CSS classes for the frame. |
Events
| Event | Payload | Description |
|---|---|---|
complete | - | Fired once the scratched area passes threshold. It does not fire again until reset() is called. |
Slots
| Slot | Props | Description |
|---|---|---|
default | - | Content under the cover. It is always in the document, so a screen reader can read it before anyone scratches anything. |
Methods
| Prop | Type | Default | Description |
|---|---|---|---|
reset | () => void | - | Paints the cover back and clears the revealed state, ready to be scratched again. |
<template>
<MScratchToReveal ref="scratch">
<p>Under the cover</p>
</MScratchToReveal>
<UButton @click="scratch?.reset()">
Play again
</UButton>
</template>
UI
This component's theme exposes the slots below. Override any of them per instance with the ui prop, or app-wide under ui.scratchToReveal in app.config.ts.
| Prop | Type | Default | Description |
|---|---|---|---|
ui | object | - | Per-slot classes merged over the theme defaults. |
| Slot | Props | Description |
|---|---|---|
root | - | relative w-full select-none overflow-hidden rounded-xl |
content | - | flex size-full items-center justify-center |
canvas | - | absolute inset-0 size-full cursor-crosshair touch-none transition-opacity duration-500 |
Accessibility
The cover is a canvas with aria-hidden, and the content underneath is ordinary DOM that is never removed, so nothing has to be uncovered to be read or found by search. Scratching is pointer-only, which is why the prize should not be a secret that only appears on success: the reveal is a flourish on top of information that is already in the page. On touch screens the canvas sets touch-action: none, so a drag scratches instead of scrolling the page.