212 lines
9.8 KiB
JavaScript
212 lines
9.8 KiB
JavaScript
/**
|
|
* 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 = `<svg viewBox="0 0 100 100" width="${this.options.size}px" height="${this.options.size}px">\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 += ` <line x1="${x1}" x2="${x2}" y1="${y1}" y2="${y2}" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-linecap="${strokeLinecap}" stroke-linejoin="${strokeLinejoin}" fill="${fill}"></line>\n`;
|
|
});
|
|
|
|
svgCode += `</svg>`;
|
|
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;
|
|
}
|
|
} |