svg-particle-lab is a React component and demo lab for importing SVG artwork,
rasterizing it in the browser, and rendering it as thousands of interactive 2D
canvas particles. Hover pushes the dots apart. Click fires a shockwave. When the
particles settle, the animation loop stops. Users who prefer reduced motion get
the same static particle render without pointer-driven motion.
SVGs are easy to ship, but most particle treatments require hand-authored coordinates, WebGL, or one-off logo code. This package keeps the workflow simple: bring an SVG, choose a sampling grid, and let the browser turn the vector into a typed-array particle field.
The technique was inspired by Emil Kowalski's interactive dithered logo work in Agents with Taste. This repo generalizes the idea into an SVG importer and reusable React renderer. No code from Emil's article is copied here.
npm install svg-particle-labReact is a peer dependency.
import { DitheredSvgLogo } from 'svg-particle-lab';
export function LogoParticles() {
return (
<DitheredSvgLogo
svgSrc="/logo.svg"
grid={256}
fit="contain"
size={360}
onError={(error) => console.error(error)}
aria-label="Logo particle preview"
/>
);
}npm install
npm run devThe demo includes drag-and-drop SVG import, grid controls, fit controls, size controls, render stats, JSX snippet copying, and JSON preset export.
| Prop | Type | Default | Description |
|---|---|---|---|
svgSrc |
string |
required | SVG URL. Same-origin paths, object URLs, and CORS-enabled remote URLs work best. |
grid |
number |
256 |
Square rasterization grid. Higher values create more particles and sharper detail. |
fit |
"stretch" | "contain" |
"stretch" |
How the SVG is drawn into the square sampling grid. |
size |
number |
undefined |
CSS pixel width for the square canvas wrapper. |
className |
string |
undefined |
Class applied to the wrapper element. |
style |
CSSProperties |
undefined |
Inline styles merged onto the wrapper element. |
onStats |
(stats) => void |
undefined |
Called after extraction with particle count, color count, and grid size. |
onError |
(error) => void |
undefined |
Called when the SVG cannot be loaded or rasterized. |
aria-label |
string |
"Dithered SVG particle canvas" |
Accessible label for the visual. |
type DitheredSvgStats = {
dotCount: number;
colorCount: number;
grid: number;
};- Load the SVG as an image.
- Rasterize it into an offscreen square canvas.
- Sample visible pixels into
(x, y, colorIndex)triples. - Quantize RGB channels into color buckets.
- Draw all particles in a batched
fillRectpass, onefillStyleper bucket. - Apply pointer repulsion and click shockwaves with preallocated typed arrays.
- Stop
requestAnimationFrameonce the particles settle.
- Uses
Float32Array,Int16Array, andInt32Arrayfor particle state. - Uses
willReadFrequently: truefor the extraction canvas. - Buckets particles by quantized color to avoid one
fillStylechange per dot. - Filters hover work to mouse pointers; touch still gets click/tap shockwaves.
- Snaps tiny displacements to zero so idle CPU returns to zero after motion.
- Respects
prefers-reduced-motion: reduceby rendering the static particle field without attaching pointer animation handlers.
The component exposes an aria-label prop for the visual canvas. Accessibility
expectations for labels, keyboard controls, reduced motion, and docs are in
ACCESSIBILITY.md.
- This is browser-only. It needs DOM, canvas, pointer events, and image loading.
- SVGs are rasterized by the browser, so browser SVG support applies.
- Remote SVGs can taint the canvas unless served with compatible CORS headers. Prefer same-origin assets or object URLs from local uploads.
- Very large or complex SVGs should use a lower
gridvalue first. - The renderer currently targets square canvases.
- This package does not sanitize untrusted SVGs. Validate and sanitize uploaded or remote SVG content before using it in production flows.
npm install
npm run verify
npm run typecheck
npm run build
npm run lintnpm run verify is the CI path and runs typecheck, lint, and build.
See SECURITY.md for reporting and SVG handling boundaries.
MIT.
