Sparkle Field
A shimmering field of dots in a single element: a repeated dot mask intersected with an animated Perlin-noise mask via mask-composite, so the dots twinkle. No JS, no canvas.
Usage
import { SparkleField } from "~/components/effects/sparkle-field";<div className="h-72 overflow-hidden rounded-xl"><SparkleField /></div>
Two masks, intersected
The element is just a gradient. The magic is two stacked mask layers: a radial-gradient repeated into a grid of dots, and a tile of Perlin noise. mask-composite: intersect keeps only the pixels where both masks are opaque, so a dot shows only where the noise happens to be bright underneath it.
Animate the noise layer's mask-position and that bright region drifts across the grid, lighting different dots each frame. That's the twinkle, and it's one element doing it instead of thousands of animated nodes. I generate the noise with an inline SVG feTurbulence filter, so there's no image to ship.
.sparkle-field {background: var(--gradient);/* layer two masks: the dots, then the noise */mask-image: var(--dots), var(--noise);mask-composite: intersect; /* keep only where both show */animation: flicker 8s linear infinite alternate;}@keyframes flicker {to { mask-position: 0 0, 120px 90px; } /* slide the noise layer */}
Props
| prop | type | default | description |
|---|---|---|---|
| gradient | string | purple→cyan | Any CSS background — the colors the sparkles reveal. |
| gap | number | 40 | Dot grid spacing in px. |
| speed | number | 8 | Seconds per shimmer cycle. |
| className / style | — | — | Forwarded to the element (set a height). |
Recreated from jhey.
Source
download .zipThe full implementation, across 2 files. Copy it in or grab the zip. It leans on a cn() class helper and the theme tokens (--primary, --border, …).
import type { CSSProperties } from "react";import { cn } from "~/lib/utils";export interface SparkleFieldProps {/** Any CSS background — the colors that show through the sparkles. */gradient?: string;/** Grid spacing of the dots, in px. */gap?: number;/** Seconds for one shimmer cycle. */speed?: number;className?: string;style?: CSSProperties;}const DEFAULT_GRADIENT ="radial-gradient(circle at 22% 18%, #c4b5fd, transparent 55%), radial-gradient(circle at 82% 78%, #67e8f9, transparent 55%), linear-gradient(120deg, #3b82f6, #8b5cf6 52%, #ec4899)";/*** A shimmering field of dots in one element. A repeated radial-gradient (the* dots) is intersected with an animated Perlin-noise mask via* mask-composite: intersect, so only the dots under "bright" noise show, and* sliding the noise makes them twinkle. Recreated from jhey (@jh3yy).*/export function SparkleField({gradient = DEFAULT_GRADIENT,gap = 40,speed = 8,className,style,}: SparkleFieldProps) {return (<divclassName={cn("sparkle-field", className)}style={{background: gradient,"--sparkle-gap": `${gap}px`,"--sparkle-speed": `${speed}s`,...style,} as CSSProperties}/>);}
/* ── Sparkle field (dots × noise via mask-composite), from jhey ───────────── */.sparkle-field {--sparkle-gap: 40px;--sparkle-speed: 8s;--sparkle-dots: radial-gradient(circle at 50% 50%, #fff 2px, transparent 2.6px);--sparkle-noise: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='256' height='256'%3E%3Cfilter id='n' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.028' numOctaves='3' seed='4' stitchTiles='stitch'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='2.2' intercept='-0.25'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='256' height='256' filter='url(%23n)'/%3E%3C/svg%3E");width: 100%;height: 100%;-webkit-mask-image: var(--sparkle-dots), var(--sparkle-noise);mask-image: var(--sparkle-dots), var(--sparkle-noise);-webkit-mask-size: var(--sparkle-gap) var(--sparkle-gap), 256px 256px;mask-size: var(--sparkle-gap) var(--sparkle-gap), 256px 256px;-webkit-mask-position: 0 0, 0 0;mask-position: 0 0, 0 0;-webkit-mask-repeat: repeat, repeat;mask-repeat: repeat, repeat;-webkit-mask-composite: source-in;mask-composite: intersect;animation: sparkle-flicker var(--sparkle-speed) linear infinite alternate;}@keyframes sparkle-flicker {to {-webkit-mask-position: 0 0, 120px 90px;mask-position: 0 0, 120px 90px;}}@media (prefers-reduced-motion: reduce) {.sparkle-field { animation: none; }}