Skip to content

Getting started

Install Morph, make a morph, and draw any moment of it: in React, in the page, or on a server.

@cesarvenzac/morphv1.0.0
npm install @cesarvenzac/morph

Plain TypeScript, published as ES modules with their types, and no dependencies. React is an optional peer, for the React entry alone, and only the DOM entry needs a browser.

A first morph

A morph is made from two sources and drawn at any moment between them. Making it is the costly part, where the pieces are matched, so make it once and keep it:

import { morph } from "@cesarvenzac/morph";
import { Plus, X } from "lucide";

const m = morph(Plus, X);
const frame = m.at(0.5); // halfway

at(t) takes any t from 0, the first side exactly, to 1, the second exactly, and returns the frame as parts: a <path> each, its path data and how it's painted. Between, t can come from anywhere: a spring, a tween, a scroll position, a slider.

Plus to X
Scrub the slider: every moment is a few paths, each with its share of the stroke's width.

Draw it

In React, MorphFrame draws a morph at the t you give it, and useMorph keeps the morph across renders, made again only when a side or the options change:

import { MorphFrame, useMorph } from "@cesarvenzac/morph/react";
import { Sun, Moon } from "lucide";

export function Dial({ t }: { t: number }) {
  const m = useMorph(Sun, Moon);
  return <MorphFrame morph={m} t={t} size={48} />;
}

Without React, draw puts a frame into an <svg> on the page, a <path> a part, and svgOf writes one as an SVG document, for a server, an <img> or a file:

import { svgOf } from "@cesarvenzac/morph";
import { draw } from "@cesarvenzac/morph/dom";

draw(document.querySelector("svg")!, m.at(t), m.viewBox);

svgOf(m, 0.5); // '<svg xmlns="http://www.w3.org/2000/svg" …'

Play it

For an icon that changes with a state, as a button's does, MorphIcon springs to each new icon, and turns back from where it is when it's sent back mid-way:

import { MorphIcon } from "@cesarvenzac/morph/react";
import { Menu, X } from "lucide";

<button
  onClick={() => setOpen(!open)}
  aria-label={open ? "Close menu" : "Open menu"}
>
  <MorphIcon icon={open ? X : Menu} />
</button>;

Name the button, not the icon: MorphIcon is hidden from assistive tech unless it's given a name of its own. Under prefers-reduced-motion it changes at once. Playing it has the rest: the spring, mount for the DOM, and motion for any other clock.

Beyond icons

Either side can be any SVG, read from its markup, or a word set in a font: What it morphs has every kind.

import { morph, svg, text } from "@cesarvenzac/morph";

morph(svg(heartMarkup), svg(starMarkup));
morph(text(font, "Hello"), text(font, "World"));