Scratch Reveal
Digital scratch card: a canvas covers a text reward; scratch with mouse or finger, or click "Reveal all" to clear it in one go.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Hover has 48 effects, including 5 free. Explore the category →
3 usage examples



How it works
The .fx-0375-root frame holds a .sck-container that layers a .sck-reveal div (the reward: text, background colour) and a .sck-cover canvas positioned absolute on top. On load, setup() uses fixed internal dimensions of W=300 px and H=150 px (hard-coded), and multiplies by devicePixelRatio for the canvas resolution, then fills the canvas with a solid grey. The display size of the canvas (CSS on .sck-container) may differ from 300×150 — scratch coordinates are normalised by scaleX = W / rect.width and scaleY = H / rect.height to always cover the full visible surface. On a Retina screen, the internal canvas is twice its displayed size, keeping scratch edges sharp.
mousemove and touchmove events on the canvas erase a 40 px radius circle at the pointer's position via ctx.globalCompositeOperation = 'destination-out': this operation makes the canvas transparent where the pointer passes, revealing .sck-reveal underneath. touchmove calls preventDefault() to block scrolling. Erasing is permanent: there is no automatic reset.
A .sck-reveal-btn button calls ctx.clearRect() to clear the entire canvas in one click. The .sck-reveal div carries aria-live="polite" so its content is announced by screen readers.
Accessibility
- Reduced motion: scratching is a user gesture — there is no automatic animation to disable. The experience is identical under
prefers-reduced-motion: reduce. The "Reveal all" button lets users uncover the reward without scratching. - "Reveal all" button: a native
<button>clears the entire canvas in one click or key press — an alternative to scratching for users who cannot use a mouse. - Reward announcement: the
.sck-revealdiv carriesaria-live="polite"; its content is announced by screen readers when it becomes perceptible (after full scratch or "Reveal all" click). Note:aria-liveonly announces content when the text changes; make sure the text is present from load. - Decorative canvas: the scratch canvas carries
aria-hidden="true". All useful information is in.sck-reveal.
Browser compatibility
2D canvas (destination-out) + 2 KB of JavaScript, no dependency. The destination-out operation is available in all modern browsers. devicePixelRatio available everywhere. Touch events available on all modern mobiles. Measured in Chromium 151, Firefox 153 and WebKit 26: scratching, full reveal and aria-live announcement identical in all three. On Safari iOS, touch scratching works correctly with touch-action: none.
Without JavaScript, the canvas stays in place and hides .sck-reveal — the reward is not visible. It is advisable to add a static reveal link or button visible without JavaScript.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0375-root">
<div class="sck-wrap">
<div class="sck-container">
<div class="sck-reveal" aria-live="polite">YOU WON!</div>
<canvas class="sck-cover" aria-hidden="true"></canvas>
</div>
<button class="sck-reveal-btn" type="button">Reveal all</button>
<span class="sck-label" aria-hidden="true">Scratch to reveal</span>
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Reward to reveal (HTML — content of .sck-reveal) |
"YOU WON!" on a coloured background | Replace with any HTML: a promo code, an SVG image, a profile text. The .sck-reveal background is fully customisable in CSS. |
Scratch brush radius (JS — radius = 40) |
40 px | Lower to 20 px for more precise scratching (thin finger); raise to 60-80 px to clear quickly with a broad movement. |
Canvas colour (JS — fillStyle in setup()) |
grey (#aaa) | Switch to #c0a060 for a golden tint, #b0b0b0 for silver. You can also use a gradient (createLinearGradient) or draw a pattern before calling fill(). |
Reveal button label (HTML — content of .sck-reveal-btn) |
"Reveal all" | Replace with "Reveal now", "I give up!" or any contextual label. |
Canvas size (CSS — .sck-container) |
300×150 px | Adjust the container's width and height. The canvas automatically fits those dimensions via setup(). |
FAQ
ctx.getImageData() and count transparent pixels (alpha channel = 0). If the ratio exceeds 70%, trigger your action (validation animation, API call). Note: getImageData() can be expensive on a large canvas — throttle checks (every 50 ms, for example).setup() again: it clears and refills the canvas. You can expose this function from outside by assigning it to root._reset = setup.