API reference
Every function and type the package exports, entry by entry: the core, React and the DOM.
Three entries: the core, which runs anywhere, @cesarvenzac/morph/react and @cesarvenzac/morph/dom. The core touches neither React nor the DOM, its types included.
The core
morph(from, to, options?)
A morph between two sources. options is a way, "soft" (the default), "even", "index" or Steps, or { way?, extra? }.
function morph(from: Source, to: Source, options?: Method | Steps | Options): Morph;
type Morph = {
slots: number; // parts in every frame, some empty at the ends
viewBox: Box; // the canvas every frame is drawn on
at(t: number): Part[]; // the frame at t, 0 to 1
shapes(t: number): Shape[]; // the same, as anchors and handles
points(t: number): Vec[][]; // each part's outline points
};svg(markup)
A drawing from SVG markup, read without a DOM: What it morphs says what's read.
function svg(markup: string): Drawing;text(font, value, options?)
A word or a line set in a font, each glyph a filled part.
function text(font: Font, value: string, options?: TextOptions): Drawing;
type TextOptions = { size?: number; color?: string };motion(source, options?)
A drawing at rest on source that springs to the next when told: pure and immutable, each method returning a new motion.
function motion(source: Source, options?: MotionOptions): Motion;
type MotionOptions = Options & { spring?: Spring };
type Motion = {
readonly morph: Morph;
readonly t: number;
readonly velocity: number; // in t a second
readonly target: 0 | 1;
readonly source: Source; // headed for, or resting on
readonly settled: boolean;
readonly viewBox: Box;
toward(next: Source): Motion;
step(seconds: number): Motion;
jump(): Motion;
frame(): Part[];
};attributesOf(part, options?)
A part as a <path>'s attributes, for setAttribute or markup. An icon's part is strokeWidth wide (2 unless said) at a width of 1.
function attributesOf(part: Part, options?: PaintOptions): Attributes;
type PaintOptions = { strokeWidth?: number };
type Attributes = {
d: string;
fill: string;
stroke: string;
"fill-opacity"?: string;
"fill-rule"?: "evenodd";
"stroke-width"?: string;
"stroke-linecap"?: "butt" | "round" | "square";
"stroke-linejoin"?: "miter" | "round" | "bevel";
opacity?: string;
};svgOf(morph, t, options?)
The frame at t as an SVG document. color is what the current colour is; width and height give it a size, without which it fills what holds it.
function svgOf(m: Morph, t: number, options?: SvgOptions): string;
type SvgOptions = PaintOptions & {
color?: string;
width?: number;
height?: number;
};nodesData(nodes, closed, digits?)
Nodes back as path data, three decimals unless said: for drawing anchors and handles, or a frame from shapes.
function nodesData(nodes: Node[], closed: boolean, digits?: number): string;EVENLY, SOFTLY, PRESS
Soft's steps all off and all on, and the spring that settles without overshoot, { stiffness: 260, damping: 32 }.
Types
type Source = Icon | Drawing;
type Icon = string | readonly string[] | readonly IconElement[];
type IconElement = readonly [tag: string, attributes: Record<string, string | number | undefined>];
type Drawing = { parts: DrawingPart[]; viewBox: Box; em?: number };
type DrawingPart = {
kind: "fill" | "stroke";
d: string;
color?: string;
opacity?: number;
width?: number;
rule?: "nonzero" | "evenodd";
cap?: "butt" | "round" | "square";
join?: "miter" | "round" | "bevel";
};
type Box = [x: number, y: number, width: number, height: number];
type Part = {
d: string;
width: number;
fill?: number; // drawings: 0 a stroke, 1 a fill
color?: string;
opacity?: number;
cap?: "butt" | "round" | "square";
join?: "miter" | "round" | "bevel";
};
type Shape = { nodes: Node[]; closed: boolean; width: number; holes?: Contour[] };
type Contour = { nodes: Node[]; closed: boolean };
type Node = { at: Vec; in?: Vec; out?: Vec };
type Vec = [number, number];
type Method = "soft" | "even" | "index";
type Steps = { shape: boolean; whole: boolean; round: boolean; curves: boolean };
type Extra = "auto" | "split" | "gather";
type Options = { way?: Method | Steps; extra?: Extra };
type Spring = { stiffness: number; damping: number };
type Font = {
unitsPerEm: number;
ascender: number;
descender: number;
getPath(text: string, x: number, y: number, size: number): FontPath;
getPaths?(text: string, x: number, y: number, size: number): FontPath[];
getAdvanceWidth(text: string, size: number): number;
charToGlyph?(char: string): FontGlyph;
getKerningValue?(left: FontGlyph, right: FontGlyph): number;
};
type FontGlyph = {
advanceWidth?: number;
getPath(x: number, y: number, size: number): FontPath;
};
type FontPath = { toPathData(decimals?: number): string };React
@cesarvenzac/morph/react: client components and hooks, React 18 or later.
<MorphIcon>
An icon, or any drawing, that springs to the next whenever icon changes. Any <svg> prop goes on its <svg>; it's hidden from assistive tech unless it's named.
type MorphIconProps = SVGProps<SVGSVGElement> & {
icon: Source;
size?: number | string; // 24
strokeWidth?: number; // an icon's, 2
spring?: Spring;
way?: Method | Steps;
extra?: Extra;
reducedMotion?: boolean; // prefers-reduced-motion
};useMorph(from, to, options?)
A morph kept across renders, made again only when a side or the options say something new: icons written inline are the same icons.
useMorphing(source, options?)
MorphIcon's motion without its <svg>: { parts, viewBox, motion }, springing to each new source.
<MorphFrame>
A morph drawn at t: morph, t, size and strokeWidth, any other prop on its <svg>.
The DOM
@cesarvenzac/morph/dom, for a page without a framework.
mount(svg, source, options?)
function mount(svg: Element, source: Source, options?: MountOptions): Mounted;
type MountOptions = MotionOptions & PaintOptions & { reducedMotion?: boolean };
type Mounted = {
to(next: Source): void; // spring there
set(next: Source): void; // show it at once
readonly motion: Motion;
stop(): void;
};draw(svg, parts, viewBox?, options?)
Parts into an <svg>: a <path> each, made and taken away as their number changes, drawn after whatever else the <svg> holds, each with no attribute left over from the frame before.
Cost
Making a morph is the costly part: about 8 milliseconds for a pair of Lucide's icons (15 for nine pairs in ten), and about 100 for two words of a dozen letters, measured in Node on one core of a 2.1 GHz server. Do it once and keep the morph, not the sources. A frame then takes a quarter of a millisecond for icons, and 2 to 4 for words.