diff --git a/README.md b/README.md new file mode 100644 index 0000000..53cb926 --- /dev/null +++ b/README.md @@ -0,0 +1,219 @@ +# Hamburger Morphing + +A lightweight ES module for creating animated hamburger menu icons that can morph into various shapes using SVG.js. + +## Features + +- **Smooth Animations**: 500ms morphing animations between shapes +- **Configurable**: Customize size and color +- **Lightweight**: Minimal dependencies (only SVG.js) +- **ES Module**: Modern JavaScript module format +- **Multiple Shapes**: Support for 8 different icon shapes + +## Installation + +### As a Git Submodule + +Add this project as a submodule to your repository: + +```bash +git submodule add 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 + +```html + + + + + + + + + + + +``` + +## API Reference + +### Constructor + +```javascript +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 + - `color` (string, default: "#000"): Stroke color for the lines + +**Example:** +```javascript +const hamburger = new HamburgerMorphing('#menu', { + size: 60, + color: '#333' +}); +``` + +### 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 + +**Example:** +```javascript +hamburger.animateTo('x'); +hamburger.animateTo('arrow_right'); +``` + +#### `getSvgCode()` + +Returns the current SVG markup as a string. + +**Returns:** (string) SVG markup + +**Example:** +```javascript +const svgCode = hamburger.getSvgCode(); +console.log(svgCode); +// Output: ... +``` + +#### `destroy()` + +Cleans up the hamburger instance and removes the SVG from the DOM. + +**Example:** +```javascript +hamburger.destroy(); +``` + +## Complete Example + +```html + + + + Hamburger Menu Demo + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + + +``` + +## 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](LICENSE). + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Test thoroughly +5. Submit a pull request + +## Demo + +Open `hamburger.html` in your browser to see all available shapes and animations in action. \ No newline at end of file diff --git a/hamburger.html b/hamburger.html index 95ef6d6..ea44430 100644 --- a/hamburger.html +++ b/hamburger.html @@ -21,9 +21,26 @@ flex-direction: row; justify-content: center; align-items: center; - width: 100px; - height: 100px; + width: 300px; + height: 300px; margin-bottom: 20px; + border: 1px solid #000; + position: relative; + } + + #divSvgOut::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-image: + radial-gradient(circle, #ff0000 1px, transparent 1px); + background-size: 33.333px 33.333px; + background-position: 0px 0px; + pointer-events: none; + z-index: 1; } button { margin: 5px; @@ -37,7 +54,7 @@

Hamburger Morphing

- + @@ -45,134 +62,36 @@ + + + + + + + +
- \ No newline at end of file diff --git a/hamburger.mjs b/hamburger.mjs new file mode 100644 index 0000000..57b680c --- /dev/null +++ b/hamburger.mjs @@ -0,0 +1,212 @@ +/** + * 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; + } +} \ No newline at end of file