Particles
Morph a word, shape or image into another as dots: each dot is matched to its nearest place in the next one.
Choose Image in the demo to see the field jacket photograph become an olive-and-brass halftone, then switch back to text or an SVG shape to watch the dots reform. A dark-pixel mask separates the jacket from its light backdrop. The demo scales its dot count with the preview size, up to 9,000 on wide screens. The photograph is a local demo asset, not part of the package.
Installation
npm install @remold-ui/reactImport particles from @remold-ui/react/canvas so canvas rendering stays opt-in.
Using plain JavaScript? Install @remold-ui/canvas instead.
Usage
Give Remold.Particles a shape, and change it to morph. Each dot heads for its nearest place in the new shape; spare dots shrink away and new ones grow in. Dots near the pointer lean away.
import { Remold } from '@remold-ui/react/canvas';
<Remold.Particles shape="Just landed" className="h-64 w-full" />
<Remold.Particles shape={{ path: HEART }} />
<Remold.Particles shape={{ image: '/logo.png', by: 'dark' }} />
<Remold.Particles
shape={{
image: '/returns-field-jacket.jpg',
by: 'dark',
threshold: 100,
pattern: 'grid',
jitter: 0,
}}
count={9000}
palette={['#23251b', '#484d37', '#707456', '#8a8060', '#aa946e']}
/>
<Remold.Particles shape={{ image: '/flower.png' }} palette={['#2563eb', '#8d9bf8', '#eee4bd']} />
<Remold.Particles shape={{ points: myPoints }} />In plain JavaScript you sample each shape into points yourself and pass them to to. An image must have loaded before it's sampled.
shape | Dots fill |
|---|---|
"words" or { text, font } | Text in a CSS font. Load web fonts first. |
{ path, fillRule, mode } | An SVG path's inside, or its outline with mode: "outline". |
{ image, by, threshold, pattern, jitter, details } | An image's opaque, dark, light, or saturated pixels. Use pattern: "grid" and jitter: 0 for regular halftone dots; details adds small features the mask omits. |
{ points } | Your own points. A new array is a new shape. |
count sets how many dots (default 1200). Keep it the same for every shape and no dots need to arrive or leave: each one just travels. pairing="index" sends the first point to the first point, and so on, for shapes from the same generator (equaliser bars, a waveform).
Scatter between shapes
By default each dot springs straight to its new place. transition="scatter" gives the change a middle instead: the shape dissolves into a loose, glittering cloud, holds there a moment, then gathers into the next one, each dot setting off at a slightly different time. Try Scatter and Direct in the demo above.
<Remold.Particles shape={next} transition="scatter" />
<Remold.Particles shape={next} transition={{ type: "scatter", spread: 1.6, hold: 0.5, stagger: 0.4 }} />spread is how wide the cloud gets, hold how long it stays spread, and stagger how unevenly the dots set off again. See particles() for each one. With reduced motion the dots go straight to the new shape.
3D shapes
Build a shape from shapes in @remold-ui/core and pass it straight in. Give it a camera and it turns, with far dots drawn smaller. Shapes are built from a few primitives, moved and coloured with chained methods, and combined with compose:
import { shapes } from '@remold-ui/core';
import { Remold } from '@remold-ui/react/canvas';
const galaxy = shapes.spiral({ arms: 3 }).tone(({ group }) => (group === 3 ? 'core' : 'arm'));
const planet = shapes.compose([
{ shape: shapes.sphere().tone('rock') },
{ shape: shapes.oval({ radii: [1.8, 1.8], edge: 1 }).rotate({ x: 75 }).tone('ice') },
]);
const vase = shapes.revolve({ profile: [[0.3, -1], [0.7, -0.4], [0.35, 0.5], [0.5, 1]] });
const flower = shapes.oval({ radii: [0.5, 0.18] }).at([0.5, 0, 0]).repeat({ copies: 6, around: 'y' });
const logo = shapes.fromPath(LOGO_PATH, { depth: 0.4 });
<Remold.Particles
shape={next ? planet : galaxy}
count={3000}
palette={{ core: '#fff1d6', arm: '#8b5cf6', rock: '#f59e0b', ice: '#bae6fd' }}
pairing="flow"
camera
blend="add"
/>| Shape | What it is |
|---|---|
sphere({ radius, solid }) | A ball's surface, or through its volume with solid. |
ellipsoid({ radii, solid }) | A ball stretched along x, y and z. |
oval({ radii, edge }) | A flat oval, lined at its edge: wings, rings, leaves. |
curve({ through }) | A smooth curve through your points: stems, antennae, paths. |
torus({ radius, tube }) | A ring-shaped tube, lying flat. |
revolve({ profile }) | A side view spun round the y axis: vases, bottles, bells, lamps. |
fromPath(svgPath, { depth }) | Any SVG path made solid: an icon, a logo, a drawing. |
fromPixels(pixels, { depth, bulge, colors, by }) | A photo as a relief in its own colours, found for you (.palette). |
spiral({ arms, twist, core }) | A spiral galaxy, lying flat. |
helix({ strands, turns, radius, height, rungs }) | Twisting strands, with rungs between them. |
attractor("lorenz") | The Lorenz attractor's two wings, as one long path. |
compose([{ shape, share? }, …]) | One shape from several, drawn equally densely, or weighted by share. |
Every shape has .at([x, y, z]), .scale(by), .rotate({ x, y, z }) in degrees, .mirror(axis), .tone(name or rule), .stripes({ every, tones }), .fuzz(amount), .warp(point => [x, y, z]) for ripples and bulges, and .repeat({ copies, around, step }) for petals, teeth and stacks, each returning a new shape. compose shares the dots by each part's area, so a big wing and a small eye come out equally dense; a share weights a part by hand. A tone rule sees each point's position, how far along its shape it is, and its group (an arm, a strand, a part of a composition). Shapes are built with y pointing up and sampled the same way every time, so one shape can be 800 dots on a phone and 4000 on a desktop.
pairing="flow"pairs dots by where they sit along each shape (out from a spiral's core, down a helix, from one end of a composition to the other), so a change sweeps through as one movement instead of swarming.- A named
palettelets points name their colour. Tones still spring, so a dot changes colour on the way. cameratakestruefor the default, or{ spin, tilt, distance, sway }. Changespinto0to pause it where it is.swayswings it back and forth that many degrees instead of turning all the way round, which suits flat things like photos.
Photos in 3D
Give an image shape a depth (and a bulge to dome it) and it becomes a relief of dots in the image's own colours. Remold.Particles loads it, finds its main colours and uses them as the palette, unless you pass one.
<Remold.Particles shape={{ image: '/dahlia.png', depth: 0.25, bulge: 0.5 }} count={5000} camera={{ sway: 28 }} />A photo from another site needs to allow it (CORS), or the browser won't let its pixels be read. In plain JavaScript, dots.setPalette(next.palette) before dots.to(next.points(5000)) swaps to the next photo's colours.
Image details use normalized source-image coordinates from 0 to 1 and count toward count. A tone of 1 uses the last color in the palette, useful for eye reflections or other tiny highlights.
API reference
See particles() for every option. The React part takes the same ones as props, plus shape, count, label, pairing and transition.