Ink Arrow

A hand-drawn arrow that draws itself from one point to another in stop-motion poses

Installation

$ pnpm dlx shadcn@latest add @remocn/ink-arrow

Usage

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.

Props

PropTypeDefaultDescription
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.35Perpendicular bow as a fraction of the distance; negative bows the other way
color
string"#26242c"Ink colour (any valid CSS color)
strokeWidth
number8Brush width in pixels at full pressure
pressure
number0.2Weight the brush opens on, as a fraction of strokeWidth
release
number1Weight the brush reaches at the head; above pressure the shaft thickens as it travels
grain
number1Edge roughness; breaks the thin opening into dry-brush flecks, 0 gives clean edges
delay
number0Frames on the local clock before the drag begins
drawDur
number36Frames the contour takes to draw
headSize
number24Length of each head stroke in pixels; a floor, never shrinking below what the brush width needs
headDur
numberstep * 4Frames to draw both head strokes, split evenly between them
seed
string"arrow"Seed for the curve wobble — same seed, same arrow
step
number3Frames per stop-motion pose — the tempo control