/** * Hamburger Morphing Animation Module * A lightweight ES module for creating animated hamburger menu icons using SVG.js */ // Configuration constants - easily adjustable const STROKE_WIDTH = 29; const ANIMATION_DURATION = 500; export class HamburgerMorphing { /** * Creates a new hamburger morphing animation instance * @param {string} containerSelector - CSS selector for the container element * @param {Object} options - Configuration options * @param {number} [options.size=100] - Size of the SVG in pixels * @param {string} [options.color="#000"] - Stroke color for the lines */ constructor(containerSelector, options = {}) { this.container = document.querySelector(containerSelector); if (!this.container) { throw new Error(`Container element not found: ${containerSelector}`); } this.options = { size: options.size || 100, color: options.color || "#000" }; this.draw = null; this.line1 = null; this.line2 = null; this.line3 = null; this.stroke = { color: this.options.color, width: STROKE_WIDTH, linecap: "round", linejoin: "round", }; this.init(); } /** * Initializes the SVG drawing and creates the initial hamburger lines */ init() { // Clear any existing content this.container.innerHTML = ''; // Create SVG drawing - expand to fill container this.draw = SVG().addTo(this.container).size('100%', '100%').viewbox(0, 0, 100, 100); // Create the three lines this.line1 = this.draw.line(20, 20, 80, 20).fill("#fff").stroke(this.stroke); this.line2 = this.draw.line(20, 50, 80, 50).fill("#fff").stroke(this.stroke); this.line3 = this.draw.line(20, 80, 80, 80).fill("#fff").stroke(this.stroke); } /** * Animates the hamburger icon to a specified shape * @param {string} shape - The target shape ('burger', 'x', 'plus', 'minus', 'arrow_left', 'arrow_right', 'arrow_up', 'arrow_down') */ animateTo(shape) { if (!this.line1 || !this.line2 || !this.line3) { throw new Error('HamburgerMorphing not properly initialized'); } switch (shape) { case 'x': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 20, y2: 80 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 20, y1: 80, y2: 20 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 80, y2: 20 }); break; case 'plus': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 50, y2: 50 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 50, y1: 20, y2: 80 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 50, y1: 50, y2: 50 }); break; case 'minus': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 50, y2: 50 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 50, y1: 50, y2: 50 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 50, y1: 50, y2: 50 }); break; case 'burger': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 20, y2: 20 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 50, y2: 50 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 80, y2: 80 }); break; case 'arrow_left': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 20, y1: 20, y2: 50 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 65, x2: 80, y1: 50, y2: 50 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 20, y1: 80, y2: 50 }); break; case 'arrow_down': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 50, y1: 50, y2: 80 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 50, y1: 20, y2: 35 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 50, y1: 50, y2: 80 }); break; case 'arrow_up': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 50, y1: 50, y2: 20 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 50, y1: 80, y2: 65 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 50, y1: 50, y2: 20 }); break; case 'arrow_right': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 80, y1: 20, y2: 50 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 35, y1: 50, y2: 50 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 80, y1: 80, y2: 50 }); break; case 'triangle_up': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 50, y1: 80, y2: 20 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 80, y1: 20, y2: 80 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 80, y2: 80 }); break; case 'triangle_down': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 50, y1: 20, y2: 80 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 80, y1: 80, y2: 20 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 20, y2: 20 }); break; case 'triangle_left': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 20, y1: 20, y2: 50 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 50, y2: 80 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 80, y1: 20, y2: 80 }); break; case 'triangle_right': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 20, y2: 50 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 20, y1: 50, y2: 80 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 20, y1: 20, y2: 80 }); break; case 'carrot_right': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 20, y2: 50 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 20, y1: 50, y2: 80 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 20, y1: 50, y2: 80 }); break; case 'carrot_left': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 80, x2: 20, y1: 20, y2: 50 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 50, y2: 80 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 80, y1: 50, y2: 80 }); break; case 'carrot_up': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 50, y1: 80, y2: 20 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 80, y1: 20, y2: 80 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 80, y1: 20, y2: 80 }); break; case 'carrot_down': this.line1.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 20, x2: 50, y1: 20, y2: 80 }); this.line2.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 80, y1: 80, y2: 20 }); this.line3.animate(ANIMATION_DURATION, 0, "now").attr({ x1: 50, x2: 80, y1: 80, y2: 20 }); break; default: throw new Error(`Unknown shape: ${shape}`); } } /** * Returns the current SVG code as a string * @returns {string} SVG markup */ getSvgCode() { const svgElement = this.container.querySelector('svg'); if (!svgElement) return ''; const lines = svgElement.querySelectorAll('line'); let svgCode = `\n`; lines.forEach(line => { const x1 = Math.round(line.getAttribute('x1')); const y1 = Math.round(line.getAttribute('y1')); const x2 = Math.round(line.getAttribute('x2')); const y2 = Math.round(line.getAttribute('y2')); const stroke = line.getAttribute('stroke') || this.options.color; const strokeWidth = line.getAttribute('stroke-width') || '38'; const strokeLinecap = line.getAttribute('stroke-linecap') || 'round'; const strokeLinejoin = line.getAttribute('stroke-linejoin') || 'round'; const fill = line.getAttribute('fill') || '#fff'; svgCode += ` \n`; }); svgCode += ``; return svgCode; } /** * Destroys the hamburger morphing instance and cleans up the DOM */ destroy() { if (this.draw) { this.draw.remove(); this.draw = null; } this.line1 = null; this.line2 = null; this.line3 = null; } }