Brush Stroke Simulator
A simulated fingertip drags a blur brush across an image, revealing a pixelated overlay along its trail.
A semi-transparent fingertip glides across the frame along a chain of cubic-bezier waypoints, leaving a pixelated overlay in its wake. The reveal is driven by an SVG <mask> whose stroked <path> is rebuilt every frame from the cursor's accumulated trail — that's why the brush stroke feels continuous instead of stamping discrete dots. The cursor scales down slightly while it is "pressed" against the surface and springs back when it lifts between regions.
Installation
$ pnpm dlx shadcn@latest add @remocn/brush-stroke-simulatorUsage
// src/Root.tsx
import { Composition } from "remotion";
import { BrushStrokeSimulator } from "@/components/remocn/brush-stroke-simulator";
const BrushScene = () => (
<BrushStrokeSimulator
brushSize={70}
sweepDuration={150}
/>
);
export const RemotionRoot = () => (
<Composition
id="BrushStrokeSimulator"
component={BrushScene}
durationInFrames={180}
fps={30}
width={1280}
height={720}
/>
);Props
| Prop | Type | Default | Description |
|---|---|---|---|
brushSize | number | 70 | Diameter of the cursor and width of the reveal stroke in pixels. |
cursorColor | string | "rgba(255,255,255,0.45)" | Fill color of the semi-transparent fingertip circle. |
background | string | "#0a0a0a" | Page background behind the simulated portrait. |
baseColorA | string | "#f4a261" | Highlight tint of the simulated portrait beneath the overlay. |
baseColorB | string | "#e76f51" | Shadow tint of the simulated portrait beneath the overlay. |
overlayColor | string | "#1f1f23" | Base color of the pixelated overlay that the brush reveals. |
startFrame | number | 12 | Frame at which the brushing motion begins. Earlier frames hold the unbrushed scene. |
sweepDuration | number | 150 | How many frames the brush takes to traverse the full waypoint chain. |
speed | number | 1 | Playback speed multiplier applied to the internal frame counter. |
className | string | — | Optional className passed to the root container. |
Notes
SVG <clipPath> ignores stroke-width, so a stroked path can't be used as a clip. The reveal is built with an SVG <mask> instead — white background, black stroked trail — applied to the pixelated overlay rectangle. The black stroke punches a hole through which the sharp base layer shows.
The mask path is rebuilt from the cursor's accumulated samples every frame. At default settings that's roughly 110 sample points by the end of the sweep — fast enough, but if you crank sweepDuration past 240 frames you'll want to reduce the per-segment sample count inside sampleBrushPath.