# Morph: What it morphs

> Icons, any SVG markup, words set in a font, or drawings made by hand: either side can be any of them, and the two needn't match.

Source: https://cesarvenzac.com/morph/docs/sources

Either side of a morph can be any of these, and the two needn't be the same kind: an icon can turn into a word, or a filled drawing into a stroked one.

## Icons

An icon is drawn with strokes on a 24-unit grid, as Lucide's and Feather's are. It can be:

- SVG path data: `"M5 12h14M12 5v14"`.
- A list of it, an entry a stroke or more: `["M5 12h14", "M12 5v14"]`.
- Elements as Lucide writes its icons, `[tag, attributes][]`: anything from the `lucide` package as it is, and icons in the same shape from elsewhere. `path`, `line`, `polyline`, `polygon`, `circle`, `ellipse` and `rect` are read; any other element is an error.

Every element is taken as a stroke, so leave out any that isn't drawn, a bounding box for one. Two icons morph on their grid, and each part's `width` is a share of the icon's stroke width: 1 as drawn, narrowing to 0 as a stroke shrinks away.

*Live example on the page: Any two. Any two of Lucide's icons. Shuffle goes on from each icon it lands on to another.* Its source:

```tsx
// Shuffle never stops: each time the morph lands on its second icon, it goes on
// from there to another, at random, instead of turning back. The frame it landed
// on is the next morph's first, exactly, so there's no seam.
const clock = useClock(moving, {
  onLand: () => {
    if (!shuffle) return false; // there and back
    setFrom(to);
    setTo(anotherThan(to, from));
    return true; // from the start of the new pair
  },
});
const m = useMemo(() => morph(ICONS[from], ICONS[to], "soft"), [from, to]);
<Strokes strokes={m.at(clock.t)} />;
```

## Any SVG

`svg(markup)` reads SVG markup into a drawing, with no DOM, so it runs on a server as well as in a page:

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

const heart = svg(`<svg viewBox="0 0 24 24">
  <path fill="#e5484d" d="M12 20.5c-.3 0-.6-.1-.8-.3…Z"/>
</svg>`);

const m = morph(heart, svg(starMarkup));
```

It reads `path`, `rect`, `circle`, `ellipse`, `line`, `polyline` and `polygon`, inside groups and nested `svg`s (placed at their `x` and `y`, their own `viewBox` not applied), their transforms applied, with their fill, stroke, stroke width, fill rule, caps, joins, opacity and `currentColor`, from attributes or inline styles. A shape with a fill and a stroke is two parts. Gradients and patterns are read as the current colour, and what isn't drawn as a path (text, images, `use`, clips, masks, filters, `defs`) is passed over.

*Live example on the page: Any SVG. Drawings of our own, read from their markup: a colour mixing into another, strokes joining a fill, a hole closing, a stroke filling in.* Its source:

```tsx
// Any SVG: Morph reads the markup, with no DOM, paint and all.
import { morph, svg } from "@cesarvenzac/morph";
import { MorphFrame } from "@cesarvenzac/morph/react";

const heart = svg(`<svg viewBox="0 0 24 24">
  <path fill="var(--paint-1)" d="M12 20.5c-.3 0-.6-.1-.8-.3…Z"/>
</svg>`);
const star = svg(`<svg viewBox="0 0 24 24">
  <path fill="var(--paint-2)" d="M12 2.8 14.5 9.16 21.32 9.57…Z"/>
</svg>`);

// Rose to amber through the colours between, mixed in OKLab.
const m = morph(heart, star);
<MorphFrame morph={m} t={t} />;
```

## Words

`text(font, value)` sets a word, or a line, in a font [opentype.js](https://github.com/opentypejs/opentype.js) has read. Each glyph is a filled part, its holes found as SVG fills them; the package reads no font files itself.

```typescript
import opentype from "opentype.js";
import { morph, text } from "@cesarvenzac/morph";

const file = await fetch("/fonts/Inter-Regular.woff");
const font = opentype.parse(await file.arrayBuffer());

const m = morph(text(font, "Hello"), text(font, "World"));
```

`size` sets the size it's set at, in the drawing's units (24 unless said), and `color` its colour (the current colour unless said). opentype.js reads TrueType, OpenType and WOFF files, not WOFF2.

Any object shaped like opentype.js's fonts will do: `unitsPerEm`, `ascender`, `descender`, `getPath` and `getAdvanceWidth`, and `getPaths` for a part a glyph (without it, the line is one part). Where its shaping throws, as opentype.js 2.0 does on the features of some fonts, Inter's and DejaVu's among them, the line is set glyph by glyph instead, through `charToGlyph` and `getKerningValue`: no ligatures, the same letters.

*Live example on the page: Words. Type any two words: Inter's letters, each with its holes.* Its source:

```tsx
// Words: each letter a filled piece, its holes found as SVG finds
// them. In your code, the font is one opentype.js has read:
import opentype from "opentype.js";
import { morph, text } from "@cesarvenzac/morph";

const file = await fetch("/fonts/Inter-Regular.woff");
const font = opentype.parse(await file.arrayBuffer());

// Here, Inter's letters were read ahead of time into a font shaped
// as opentype.js's, so this page loads no font file.
const m = morph(text(font, from), text(font, to), { extra: "split" });
<MorphFrame morph={m} t={t} />;
```

## Drawings by hand

A drawing is plain data. `svg` and `text` make one, and so can you:

```typescript
import type { Drawing } from "@cesarvenzac/morph";

const badge: Drawing = {
  viewBox: [0, 0, 24, 24],
  parts: [
    { kind: "fill", d: "M4 4h16v16H4Z", color: "#0a84ff" },
    { kind: "stroke", d: "M8 12h8", width: 2, cap: "round" },
  ],
};
```

A part is a `fill` or a `stroke`, its path data in the drawing's units, with an optional `color` (any CSS colour), `opacity`, `width` (a stroke's), fill `rule` (`nonzero` or `evenodd`), `cap` and `join`. A text's `em` is its size: two drawings that both have one are taken as they are rather than fitted (below).

## One canvas for two sides

Two icons are drawn on their 24-unit grid. Otherwise every frame is drawn on the first side's canvas, `m.viewBox`, the second fitted into it as SVG fits a viewBox: centred, as large as fits. Two drawings that both have an `em`, as words from `text` do, are taken as they are instead, centred on each other, on a canvas wide enough for both. An icon among drawings is stroked 2 wide, as Lucide draws it.

Inside, every drawing is scaled to a 24-unit grid, which the morph's distances are tuned for (how far apart its points sit, how round a corner gets halfway), and scaled back as it's drawn: a drawing's own size doesn't change how it morphs.

## Morph's docs

- [Overview](https://cesarvenzac.com/morph.md): Morph any SVG into another without the spikes: icons, filled shapes, words, one shape into several.
- [Getting started](https://cesarvenzac.com/morph/docs/getting-started.md): Install Morph, make a morph, and draw any moment of it: in React, in the page, or on a server.
- [What it morphs](https://cesarvenzac.com/morph/docs/sources.md): Icons, any SVG markup, words set in a font, or drawings made by hand: either side can be any of them, and the two needn't match.
- [Shapes and colours](https://cesarvenzac.com/morph/docs/shapes.md): How pieces pair, how holes open and close, what becomes of the shapes one side has more of, and how colours mix.
- [Playing it](https://cesarvenzac.com/morph/docs/playing.md): Spring from one drawing to the next when something changes: MorphIcon in React, mount in the page, motion for any other clock.
- [Ways to morph](https://cesarvenzac.com/morph/docs/ways.md): Soft, even and index, the three ways side by side, the four steps that make soft what it is, and a frame as anchors and handles.
- [API reference](https://cesarvenzac.com/morph/docs/api.md): Every function and type the package exports, entry by entry: the core, React and the DOM.
