Snap.svg Morphing Icon Button — Path-to-Path Icon Animation Snippet

Snap.svg Morphing Icon Button · Buttons · Plain HTML, CSS & JS · Live preview

CategoryButtons

What's included

Features

Direct path-to-path morph
path.animate({ d }) interpolates coordinates, not a cross-fade of two icons.
Matched point structure
Both path strings resolve to 3 M/L segments so points map sensibly.
Combined color + shape animation
Snap animates d while a CSS transition fades stroke, both over ~420ms.
Built-in easing
mina.easeinout shapes the timing curve without a separate library.
Toggle state tracking
A boolean flag drives which path/color to morph toward next.
Accessible button markup
A real <button> with aria-label wraps the SVG for keyboard and screen readers.
Compact SVG icon
A single path element handles both icon states, no icon-swapping needed.
Live status text
A text label mirrors the button\u2019s current logical state.

About this UI Snippet

Snap.svg Morphing Icon Button — How Path Morphing Actually Works

Screenshot of the Snap.svg Morphing Icon Button snippet rendered live

Most "icon morph" buttons on the web fake it: two separate icons cross-fade, or a hamburger's three lines individually rotate and translate into an X via CSS transforms. This snippet does something more literal — the underlying d attribute of a single <path> element morphs directly from one shape's coordinates into another's, using Snap.svg's path.animate({ d: newPath }, ...).

What .animate({ d: ... }) actually interpolates

SVG path data is a sequence of commands (M, L, C, etc.) each carrying coordinate points. When you animate the d attribute, Snap.svg does not understand the *shapes* geometrically — it walks the current path's list of points and the target path's list of points, in order, and linearly interpolates each corresponding pair over the animation's duration. Point 1 of the start path glides toward point 1 of the end path, point 2 toward point 2, and so on.

Why this snippet's two paths morph cleanly

HAMBURGER = 'M6,9 L28,9 M6,17 L28,17 M6,25 L28,25' — three separate 2-point line segments (M start point, L end point), six points total.

A checkmark is naturally just two strokes — one short diagonal down, one long diagonal up — which would be only four points, structurally mismatched with the hamburger's six. This snippet's CHECKMARK string deliberately splits the first, shorter stroke into two collinear segments (M6,17 L10,21 M10,21 L14,25) purely to pad the point count back up to six, then adds the second stroke (M14,25 L28,9) — three M/L pairs, six points, matching the hamburger exactly. The extra midpoint at 10,21 sits exactly on the line between 6,17 and 14,25, so it changes nothing about how the checkmark *looks*; it only exists to give the morph a point to interpolate toward. Because both strings have the same number of commands in the same order with the same number of coordinate pairs, each point maps onto a sensible corresponding point on the other shape, and the interpolation reads as one coherent shape smoothly folding into another.

What happens when path structures don't match

If the two path strings had a different number of points or a different sequence of command types (say, one uses M/L/L and the other M/C/C with curves), Snap.svg's simple linear point interpolation has no semantic understanding of "this line corresponds to that curve." It still animates *something* — but the correspondence becomes arbitrary point-index pairing, and the mid-animation frames often look like the path is being dragged inside out or crossing over itself in an ugly, unpredictable way. Clean morphs are a path-authoring discipline, not an automatic feature: you get a good morph by deliberately constructing both path strings with matching command counts and orders, often by adding "dummy" points to the simpler shape so it structurally mirrors the more complex one.

The rest of the interaction

stroke is switched in the same click handler and eased by a CSS transition on the path, so the icon's color fades from white to green in step with the shape change (it is deliberately not animated through Snap, which reads the current rgb() value back and fails on it), and mina.easeinout (Snap's built-in easing namespace) shapes the timing curve so the morph accelerates then decelerates rather than moving at constant speed.

Build with AI

Build, Understand, Optimize, and Extend It With AI

The one thing worth really understanding in this snippet is why the two path strings were deliberately authored with matching structure. Paste the code into an AI assistant like Claude and ask it to explain, point by point, how Snap.svg\u2019s path.animate maps the six coordinate points of the hamburger path onto the six coordinate points of the checkmark path, and then ask it to predict (and you can verify by editing the code) what the mid-animation frame looks like if you deliberately mismatch the point count, say by making the checkmark path only 4 points instead of 6. To extend it: add a third morph target (like a plus or trash icon) with a menu of states, animate rotation alongside the path morph via a transform, wire the button to an actual mobile nav drawer's open/close state, or build the same effect with GSAP's MorphSVG-style approach and compare which produces a cleaner interpolation on curved paths.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a morphing icon button using Snap.svg (v0.5.1, from a CDN) in plain HTML, CSS, and JavaScript.

Requirements:
- A round button containing an inline SVG with a single <path> element, initially drawn as a 3-line hamburger icon using a path string with exactly three M/L line segments (6 coordinate points total), e.g. "M6,9 L28,9 M6,17 L28,17 M6,25 L28,25".
- A second path string for a checkmark icon. Since a checkmark naturally has only two strokes (4 points), deliberately split its shorter stroke into two collinear segments so the string ALSO resolves to three M/L segments with 6 total coordinate points, structurally matching the hamburger path for a clean morph.
- On click, toggle a boolean state and call path.animate({ d: targetPathString }, 420, mina.easeinout) via Snap.svg to morph the path's d attribute directly from one shape's coordinates to the other's. Set the stroke color with path.attr({ stroke }) and add a CSS transition on the path's stroke (about 420ms) so it fades in step; do not animate stroke through Snap, as Snap 0.5.1 throws on the computed rgb() value.
- Update a small status text label to reflect the current logical state (menu open/closed) after each toggle.
- Style the button as a dark circular icon button with a soft shadow and a subtle hover background change.
- In the component's documentation, explain that this direct path-to-path animation only produces a clean morph because both path strings have the same number of path commands in the same order with the same number of coordinate points \u2014 Snap.svg interpolates points by index, not by geometric shape understanding, so mismatched path structures produce an ugly, arbitrary-looking interpolation.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

Requires
<div class="smi-stage">
  <div class="smi-head">
    <span class="smi-tag">Snap.svg · path morphing</span>
    <h2>Morphing Icon Button</h2>
    <p>Click the button — the hamburger path morphs directly into a checkmark.</p>
  </div>
  <button class="smi-btn" id="smiBtn" aria-label="Toggle menu">
    <svg id="smiSvg" width="34" height="34" viewBox="0 0 34 34">
      <path id="smiPath" d="M6,9 L28,9 M6,17 L28,17 M6,25 L28,25" stroke="#fff" stroke-width="3" stroke-linecap="round" fill="none"></path>
    </svg>
  </button>
  <p class="smi-state" id="smiState">Menu closed</p>
</div>

Step by step

How to Use

  1. 1
    Add the Snap.svg CDNInclude snap.svg-min.js from the CDN panel — it attaches a global Snap function.
  2. 2
    Paste HTML, CSS, and JSA round icon button renders with a hamburger path.
  3. 3
    Click the buttonThe path morphs into a checkmark and the stroke eases to green.
  4. 4
    Click againIt morphs back to the hamburger, reversing color too.
  5. 5
    Swap in your own icon pairWrite two path strings with matching command counts and order.
  6. 6
    Adjust timingChange the 420ms duration or swap mina.easeinout for another mina easing.

Real-world uses

Common Use Cases

Mobile nav toggles
Hamburger-to-X or hamburger-to-check menu buttons.
Form submit confirmation
A submit icon morphing into a checkmark on success.
Teaching SVG path structure
A concrete case study in path command/point matching.
Micro-interaction libraries
A reusable pattern for any two-state icon toggle.
Delightful UI accents
Small, satisfying motion on a frequently-clicked control.

Got questions?

Frequently Asked Questions

path.animate({ d: newPathString }, duration, easing) walks the current path\u2019s sequence of coordinate points and the target path\u2019s sequence of coordinate points, in order, and linearly interpolates each corresponding pair over the animation duration. It is point-index interpolation, not shape-aware.

Snap\u2019s interpolation maps point N of the start path to point N of the end path. A checkmark naturally has only two strokes (four points), so this snippet splits its shorter stroke into two collinear segments purely to pad it to three M/L pairs (six points), matching the hamburger. Matching command counts and orders means each point has a sensible counterpart on the other shape, so the animation reads as one coherent shape folding into the other instead of an arbitrary point shuffle.

Snap.svg still animates, but the point correspondence becomes arbitrary \u2014 point 4 of a 6-point path might map onto a point that is not remotely near it visually on the target shape, and the path can look like it is being dragged inside out mid-animation instead of morphing cleanly.

Not cleanly with a direct Snap.animate({ d }) call \u2014 the command types themselves need to line up, or you need to convert both paths to the same command vocabulary (e.g. express straight lines as degenerate cubic curves) before animating, since Snap interpolates points within matching commands rather than translating between command types.

Snap.svg 0.5.1 reads the current stroke back as a computed rgb() string when animating it, and its color parser then passes that string to querySelector, which throws a Script error. Setting the stroke with path.attr and letting a CSS transition on the path fade it, over the same ~420ms, avoids the bug and stays in sync with the morph.

Author a third path string with the same three-M/L-segment structure (six points), track a small state enum instead of a boolean, and pass the appropriate target path/color to path.animate based on the current state on each click.