Ink Arrow
A hand-drawn arrow that draws itself from one point to another in stop-motion poses
$ pnpm dlx shadcn@latest add @remocn/ink-arrowUsage
from and to are in the arrow's own local space — you place the component,
the arrow draws inside it. The SVG lets its contents overflow, so the bow and
the head are never clipped.
// src/Root.tsx
import { AbsoluteFill, Composition } from "remotion";
import { InkArrow } from "@/components/remocn/ink-arrow";
const InkArrowScene = () => (
<AbsoluteFill style={{ background: "#f1eee7" }}>
<h1 style={{ position: "absolute", left: 200, top: 150 }}>Start here</h1>
<div style={{ position: "absolute", left: 300, top: 210 }}>
<InkArrow from={{ x: 20, y: 20 }} to={{ x: 300, y: 210 }} />
</div>
<div style={{ position: "absolute", left: 600, top: 400 }}>
<YourCard />
</div>
</AbsoluteFill>
);
export const RemotionRoot = () => (
<Composition
id="InkArrow"
component={InkArrowScene}
durationInFrames={120}
fps={30}
width={1280}
height={720}
/>
);The pen drags the curve over drawDur frames, quantized to the stop-motion
clock. When the line lands, the head is drawn rather than dropped in: one
stroke out from the tip, then the other, over headDur frames split evenly
between them.
curvature is the perpendicular bow as a fraction of the distance between the
two points, so the arc keeps its shape as the arrow gets longer. Flip its sign
to bow the other way. Each control point also carries a small per-seed
wobble, which is what stops the curve looking machine-drawn; the same seed
always gives the same hand.
Props14
| Prop | Type | Default |
|---|---|---|
| fromrequired | { x: number; y: number }— Start point, in the component's local space | |
| torequired | { x: number; y: number }— End point — where the head lands | |
| curvature | number0.35 Perpendicular bow as a fraction of the distance; negative bows the other way | |
| color | string"#26242c" Ink colour (any valid CSS color) | |
| strokeWidth | number8 Brush width in pixels at full pressure | |
| pressure | number0.2 Weight the brush opens on, as a fraction of strokeWidth | |
| release | number1 Weight the brush reaches at the head; above pressure the shaft thickens as it travels | |
| grain | number1 Edge roughness; breaks the thin opening into dry-brush flecks, 0 gives clean edges | |
| delay | number0 Frames on the local clock before the drag begins | |
| drawDur | number36 Frames the contour takes to draw | |
| headSize | number24 Length of each head stroke in pixels; a floor, never shrinking below what the brush width needs | |
| headDur | numberstep * 4 Frames to draw both head strokes, split evenly between them | |
| seed | string"arrow" Seed for the curve wobble — same seed, same arrow | |
| step | number3 Frames per stop-motion pose — the tempo control | |
- fromrequired{ x: number; y: number }
Start point, in the component's local space
- torequired{ x: number; y: number }
End point — where the head lands
- curvaturenumber
Perpendicular bow as a fraction of the distance; negative bows the other way
Default
0.35 - colorstring
Ink colour (any valid CSS color)
Default
"#26242c" - strokeWidthnumber
Brush width in pixels at full pressure
Default
8 - pressurenumber
Weight the brush opens on, as a fraction of strokeWidth
Default
0.2 - releasenumber
Weight the brush reaches at the head; above pressure the shaft thickens as it travels
Default
1 - grainnumber
Edge roughness; breaks the thin opening into dry-brush flecks, 0 gives clean edges
Default
1 - delaynumber
Frames on the local clock before the drag begins
Default
0 - drawDurnumber
Frames the contour takes to draw
Default
36 - headSizenumber
Length of each head stroke in pixels; a floor, never shrinking below what the brush width needs
Default
24 - headDurnumber
Frames to draw both head strokes, split evenly between them
Default
step * 4 - seedstring
Seed for the curve wobble — same seed, same arrow
Default
"arrow" - stepnumber
Frames per stop-motion pose — the tempo control
Default
3