2026-10-26 12:58:19 -04:00
.
2026-10-26 12:58:19 -04:00
2025-11-12 14:11:37 -04:00
2025-11-12 16:15:25 -04:00
.
2026-10-26 12:58:19 -04:00
.
2026-10-26 12:58:19 -04:00

Hamburger Morphing

A lightweight ES module for creating animated hamburger menu icons that can morph into various shapes using SVG.js — plus a native Rust/egui port of the same technique.

Features

  • Stacked-Layer Morphing: Three stacked SVG lines that smoothly appear and disappear behind each other, letting one icon fluidly morph into another
  • Smooth Animations: 500ms morphing animations between shapes
  • Configurable: Customize size, foreground, background, and hover colors
  • Lightweight: Minimal dependencies (only SVG.js)
  • ES Module: Modern JavaScript module format
  • Multiple Shapes: Support for 25 different icon shapes

Rust / egui Version

A native Rust port of the same stacked-layer morphing technique lives in rust/, as a Cargo workspace with two crates:

  • rust/hamburger-morphing — the reusable library crate. Add it to any egui project as a path or git dependency.
  • rust/demo — a test window for viewing and trying out the morphing.

Running the test window

cd rust
cargo run -p hamburger-morphing-demo

The test window includes:

  • Main icon — click it to toggle between ☰ and ✕
  • Shape gallery — all 25 shapes drawn side by side; click any tile to morph the main icon to it
  • Settings — sliders for size and duration, color pickers for foreground/background/hover (defaults: black on white; the background color fills the whole window)
  • Auto-cycle — automatically morphs through every shape in sequence
  • Snap buttons — jump to a shape without animating

Using the library in another egui project

[dependencies]
hamburger-morphing = { path = "path/to/rust/hamburger-morphing" } # or { git = "..." }
eframe = "0.35"
use hamburger_morphing::{HamburgerMorphing, Shape};

struct MyApp {
    burger: HamburgerMorphing,
}

impl eframe::App for MyApp {
    fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
        let ctx = ui.ctx().clone();

        if ui.button("Close").clicked() {
            self.burger.animate_to_ctx(Shape::X, &ctx);
        }

        // Paint the icon; it animates smoothly towards the target shape.
        let response = self.burger.paint(ui);

        // Toggle between ☰ and ✕ when clicked:
        if response.clicked() {
            let target = match self.burger.shape() {
                Shape::X => Shape::Burger,
                _ => Shape::X,
            };
            self.burger.animate_to_ctx(target, &ctx);
        }
    }
}

The Rust API mirrors the JavaScript one:

JavaScript Rust
new HamburgerMorphing(sel, opts) HamburgerMorphing::new(size) with .with_foreground(...) / .with_background(...) / .with_hover(...)
hamburger.animateTo('x') burger.animate_to_ctx(Shape::X, ctx) (or animate_to(shape, now_secs))
shape name strings The Shape enum (Shape::X, Shape::ArrowLeft, ...) with as_str() / FromStr for the JS names
— burger.snap_to(shape) for instant, non-animated jumps
— burger.paint(ui) returns an egui Response (click/hover)

Run the library's test suite with:

cd rust && cargo test

Installation (JavaScript)

As a Git Submodule

Add this project as a submodule to your repository:

git submodule add <repository-url> lib/hamburger-morphing

Manual Installation

  1. Download hamburger.mjs from this repository
  2. Include SVG.js in your project (CDN or npm)
  3. Import the module in your JavaScript

Quick Start

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js@3.0/dist/svg.min.js"></script>
</head>
<body>
    <div id="menu-icon"></div>

    <script type="module">
        import { HamburgerMorphing } from './lib/hamburger-morphing/hamburger.mjs';

        // Create hamburger instance
        const hamburger = new HamburgerMorphing('#menu-icon', {
            size: 80,
            foreground: '#ff6b6b',
            background: '#ffffff',
            hover: '#ff0000'
        });

        // Morph to different shapes
        hamburger.animateTo('x');      // Close/X icon
        hamburger.animateTo('burger'); // Back to hamburger
    </script>
</body>
</html>

How It Works

The morphing effect is built on one core idea: three SVG <line> elements are stacked on top of each other, and each one can smoothly appear, disappear, or repaint itself. Because the lines are layered, they can slide in and out behind one another — which is what allows a single icon to fluidly morph into 25 different shapes.

The three stacked layers

The icon always consists of exactly three lines, created in order during init():

this.line1 = this.draw.line(20, 20, 80, 20)...  // bottom layer
this.line2 = this.draw.line(20, 50, 80, 50)...  // middle layer
this.line3 = this.draw.line(20, 80, 80, 80)...  // top layer

SVG paints elements in document order, so line3 renders on top of line2, which renders on top of line1.

Appearing and disappearing

Every shape is simply a different assignment of endpoints (x1/y1/x2/y2), stroke-width, and stroke color to the three lines, animated simultaneously over 500ms:

  • Disappearing — a line vanishes by collapsing to a zero-length segment at a single point, tucking it in behind whatever is stacked above it. In the minus shape, for example, line2 and line3 both shrink into the center point (50, 50), which lies on line1's stroke, so they vanish seamlessly:

    // 'minus': lines 2 and 3 collapse into the center and disappear behind line1
    this.line2.animate(...).attr({ x1: 50, x2: 50, y1: 50, y2: 50 });
    this.line3.animate(...).attr({ x1: 50, x2: 50, y1: 50, y2: 50 });
    
  • Appearing — the reverse: a collapsed line expands from its point into its new position, so new strokes seem to grow out of the existing icon.

  • Cutouts — a line can also be repainted with the background color. Since it sits on top of the stack, it visually erases whatever is beneath it, punching a hole in the icon. This is how the doughnut (a background-colored line over a fat foreground dot) and moon (a background-colored dot sliding off the edge of a foreground dot) shapes are drawn:

    // 'doughnut': line3 becomes a background-colored "hole" on top of the stack
    this.line3.animate(...).attr({ x1: 50, x2: 50, y1: 50, y2: 50, 'stroke-width': 45, stroke: this.options.background });
    

Why the morphs look smooth

  • All three lines animate simultaneously with the same duration, so the icon never jumps between states.
  • Round line caps (stroke-linecap: round) make collapsing lines shrink into dots instead of hard-edged stubs.
  • Since every transition is just an interpolation of coordinates, width, and color, any shape can morph directly into any other shape — no keyframes, paths, or shape-specific logic required.

API Reference

Constructor

new HamburgerMorphing(containerSelector, options)

Parameters:

  • containerSelector (string): CSS selector for the container element
  • options (object, optional): Configuration options
    • size (number, default: 100): Size of the SVG in pixels
    • foreground (string, default: "#000000"): Foreground/stroke color for the lines
    • background (string, default: "#ffffff"): Background color for cutout effects
    • hover (string, default: "#ff0000"): Hover color for interactive effects

Example:

const hamburger = new HamburgerMorphing('#menu', {
    size: 60,
    foreground: '#333333',
    background: '#ffffff'
});

Methods

animateTo(shape)

Animates the hamburger icon to the specified shape.

Parameters:

  • shape (string): Target shape name

Available shapes:

  • 'burger' - Three horizontal lines (default)
  • 'x' - Close/X icon
  • 'plus' - Plus/add icon
  • 'minus' - Minus/subtract icon
  • 'arrow_left' - Left arrow
  • 'arrow_right' - Right arrow
  • 'arrow_up' - Up arrow
  • 'arrow_down' - Down arrow
  • 'triangle_up' - Triangle pointing up
  • 'triangle_down' - Triangle pointing down
  • 'triangle_left' - Triangle pointing left
  • 'triangle_right' - Triangle pointing right
  • 'carrot_right' - Carrot/play shape pointing right
  • 'carrot_left' - Carrot/play shape pointing left
  • 'carrot_up' - Carrot/play shape pointing up
  • 'carrot_down' - Carrot/play shape pointing down
  • 'circle' - Perfect circle shape
  • 'doughnut' - Doughnut shape with center cutout
  • 'moon' - Crescent moon shape
  • 'u' - Inverted U shape (∩)
  • 'u_up' - Upright U shape (U)
  • 'u_left' - Left-facing U shape (⊂)
  • 'u_right' - Right-facing U shape (⊃)
  • 'hamburger_vertical' - Hamburger with middle line hidden (☰)
  • 'hamburger_horizontal' - Horizontal hamburger with middle line hidden (⫘)

Example:

hamburger.animateTo('x');
hamburger.animateTo('arrow_right');

getSvgCode()

Returns the current SVG markup as a string.

Returns: (string) SVG markup

Example:

const svgCode = hamburger.getSvgCode();
console.log(svgCode);
// Output: <svg viewBox="0 0 100 100" width="100px" height="100px">...

destroy()

Cleans up the hamburger instance and removes the SVG from the DOM.

Example:

hamburger.destroy();

Complete Example

<!DOCTYPE html>
<html>
<head>
    <title>Hamburger Menu Demo</title>
    <script src="https://cdn.jsdelivr.net/npm/@svgdotjs/svg.js@3.0/dist/svg.min.js"></script>
    <style>
        #menu-icon {
            cursor: pointer;
            margin: 20px;
        }
        button {
            margin: 5px;
            padding: 10px 20px;
        }
    </style>
</head>
<body>
    <div id="menu-icon"></div>

    <div>
        <button onclick="changeShape('burger')">Hamburger</button>
        <button onclick="changeShape('x')">X</button>
        <button onclick="changeShape('plus')">Plus</button>
        <button onclick="changeShape('minus')">Minus</button>
        <button onclick="changeShape('arrow_left')">Arrow Left</button>
        <button onclick="changeShape('arrow_right')">Arrow Right</button>
        <button onclick="changeShape('arrow_up')">Arrow Up</button>
        <button onclick="changeShape('arrow_down')">Arrow Down</button>
        <button onclick="changeShape('triangle_up')">Triangle Up</button>
        <button onclick="changeShape('triangle_down')">Triangle Down</button>
        <button onclick="changeShape('triangle_left')">Triangle Left</button>
        <button onclick="changeShape('triangle_right')">Triangle Right</button>
        <button onclick="changeShape('carrot_right')">Carrot Right</button>
        <button onclick="changeShape('carrot_left')">Carrot Left</button>
        <button onclick="changeShape('carrot_up')">Carrot Up</button>
        <button onclick="changeShape('carrot_down')">Carrot Down</button>
        <button onclick="changeShape('circle')">Circle</button>
        <button onclick="changeShape('doughnut')">Doughnut</button>
        <button onclick="changeShape('moon')">Moon</button>
        <button onclick="changeShape('u')">∩</button>
        <button onclick="changeShape('u_up')">U</button>
        <button onclick="changeShape('u_left')">⊂</button>
        <button onclick="changeShape('u_right')">⊃</button>
        <button onclick="changeShape('hamburger_vertical')">☰</button>
        <button onclick="changeShape('hamburger_horizontal')">⫘</button>
    </div>

    <script type="module">
        import { HamburgerMorphing } from './lib/hamburger-morphing/hamburger.mjs';

        // Create hamburger with custom options
        const hamburger = new HamburgerMorphing('#menu-icon', {
            size: 100,
            foreground: '#007bff',
            background: '#ffffff',
            hover: '#ff0000'
        });

        // Make it globally available for onclick handlers
        window.changeShape = (shape) => {
            hamburger.animateTo(shape);
        };
    </script>
</body>
</html>

Browser Support

  • Modern browsers that support ES modules
  • Chrome 61+
  • Firefox 60+
  • Safari 10.1+
  • Edge 16+

Dependencies

  • SVG.js (3.0+): Required for SVG manipulation and animations

License

This project is open source and available under the MIT License.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Test thoroughly
  5. Submit a pull request

Demo

JavaScript

Open hamburger.html in your browser to see all available shapes and animations in action.

The demo includes:

  • 300x300px container with a 1px border and red dot grid overlay
  • 25 interactive buttons for all available shapes
  • SVG code output showing the current markup
  • Hover effects that change line colors on mouse interaction
  • Smooth animations between all shape transitions

Rust

cd rust && cargo run -p hamburger-morphing-demo
S
Description
A lightweight ES module for creating animated hamburger menu icons that can morph into various shapes using SVG.js.
Readme
93 KiB
Languages
Rust 62.1%
JavaScript 30.6%
HTML 6.7%
Shell 0.6%