Loomia logo.png
Menu
HomePricingBlogWork
Book a 15-min intro callhello@loomia.com
class MenuToggle {
    constructor(config = {}) {
        this.config = {
            toggleSelector: '[data-menu-toggle]',
            overlaySelector: '[data-menu-overlay]',
            openText: 'Close',
            closedText: 'Menu',
            animationDuration: 100,
            staggerDelay: 80,
            ...config
        };
        
        this.isOpen = false;
        this.initialized = false;
        
        this.init();
    }
    
    init() {
        if (this.initialized) return;
        
        this.elements = this.getElements();
        if (!this.elements.toggle || !this.elements.overlay) {
            return;
        }
        
        this.injectStyles();
        this.forceInitialStateImmediate();
        this.createArrow();
        this.bindEvents();
        this.setAriaAttributes();
        this.setupMenuItems();
        
        this.initialized = true;
    }
    
    forceInitialStateImmediate() {
        this.elements.overlay.style.transform = 'translateY(-100%)';
        this.elements.overlay.style.transition = 'none';
        this.elements.overlay.classList.add('menu-closed');
        this.isOpen = false;
        
        setTimeout(() => {
            this.elements.overlay.style.transition = 'transform 0.6s cubic-bezier(0.4, 0.0, 0.2, 1)';
        }, 50);
    }
    
    getElements() {
        return {
            toggle: document.querySelector(this.config.toggleSelector),
            overlay: document.querySelector(this.config.overlaySelector)
        };
    }
    
    setupMenuItems() {
        this.menuItems = this.elements.overlay.querySelectorAll('*:not(style):not(script)');
        this.menuItems = Array.from(this.menuItems).filter(item => {
            return item.textContent.trim().length > 0 && 
                   !item.querySelector('*') && 
                   item.offsetParent !== null;
        });
        
        this.menuItems.forEach((item, index) => {
            item.style.opacity = '0';
            item.style.transform = 'translateY(30px)';
            item.style.transition = `opacity 0.5s ease ${index * this.config.staggerDelay}ms, transform 0.5s ease ${index * this.config.staggerDelay}ms`;
        });
    }
    
    injectStyles() {
        if (document.getElementById('menu-toggle-styles')) return;
        
        const style = document.createElement('style');
        style.id = 'menu-toggle-styles';
        style.textContent = `
            ${this.config.overlaySelector} {
                position: fixed !important;
                top: 0 !important;
                left: 0 !important;
                width: 100vw !important;
                height: 100vh !important;
                z-index: 9999 !important;
            }
            
            body.menu-open .brx-header {
                z-index: 1 !important;
            }
            
            .menu-arrow {
                display: inline-block;
                margin-right: 8px;
                transition: transform 0.4s cubic-bezier(0.4, 0.0, 0.2, 1);
                width: 8px;
                height: 8px;
                border-right: 2px solid currentColor;
                border-bottom: 2px solid currentColor;
                transform: rotate(45deg);
                transform-origin: center;
                vertical-align: middle;
                position: relative;
                top: -1px;
            }
            
            .menu-arrow.rotated {
                transform: rotate(-135deg);
            }
            
            body.menu-open {
                overflow: hidden !important;
            }
        `;
        document.head.appendChild(style);
    }
    
    createArrow() {
        this.arrow = document.createElement('span');
        this.arrow.className = 'menu-arrow';
        this.arrow.setAttribute('aria-hidden', 'true');
        
        this.textNode = document.createTextNode(this.config.closedText);
        
        this.elements.toggle.innerHTML = '';
        this.elements.toggle.appendChild(this.arrow);
        this.elements.toggle.appendChild(this.textNode);
    }
    
    setAriaAttributes() {
        this.elements.toggle.setAttribute('aria-expanded', 'false');
        this.elements.toggle.setAttribute('aria-controls', this.elements.overlay.id || 'menu-overlay');
        this.elements.overlay.setAttribute('aria-hidden', 'true');
    }
    
    bindEvents() {
        this.boundClickHandler = this.handleClick.bind(this);
        this.boundKeyHandler = this.handleKeydown.bind(this);
        
        this.elements.toggle.addEventListener('click', this.boundClickHandler);
        this.elements.toggle.addEventListener('keydown', this.boundKeyHandler);
    }
    
    handleKeydown(e) {
        if (e.key === 'Enter' || e.key === ' ') {
            e.preventDefault();
            this.toggle();
        }
        if (e.key === 'Escape' && this.isOpen) {
            this.close();
        }
    }
    
    handleClick(e) {
        e.preventDefault();
        this.toggle();
    }
    
    toggle() {
        this.isOpen ? this.close() : this.open();
    }
    
    open() {
        if (this.isOpen) return;
        
        document.body.classList.add('menu-open');
        this.elements.overlay.classList.remove('menu-closed');
        this.elements.overlay.style.transform = 'translateY(0)';
        this.textNode.textContent = this.config.openText;
        this.arrow.classList.add('rotated');
        
        setTimeout(() => {
            this.menuItems.forEach((item, index) => {
                setTimeout(() => {
                    item.style.opacity = '1';
                    item.style.transform = 'translateY(0)';
                }, index * this.config.staggerDelay);
            });
        }, 300);
        
        this.elements.toggle.setAttribute('aria-expanded', 'true');
        this.elements.overlay.setAttribute('aria-hidden', 'false');
        
        this.isOpen = true;
    }
    
    close() {
        if (!this.isOpen) return;
        
        document.body.classList.remove('menu-open');
        
        this.menuItems.forEach((item) => {
            item.style.opacity = '0';
            item.style.transform = 'translateY(30px)';
        });
        
        setTimeout(() => {
            this.elements.overlay.style.transform = 'translateY(-100%)';
            this.elements.overlay.classList.add('menu-closed');
        }, 200);
        
        this.textNode.textContent = this.config.closedText;
        this.arrow.classList.remove('rotated');
        
        this.elements.toggle.setAttribute('aria-expanded', 'false');
        this.elements.overlay.setAttribute('aria-hidden', 'true');
        
        this.isOpen = false;
    }
    
    destroy() {
        if (!this.initialized) return;
        
        this.elements.toggle.removeEventListener('click', this.boundClickHandler);
        this.elements.toggle.removeEventListener('keydown', this.boundKeyHandler);
        
        document.body.classList.remove('menu-open');
        
        const style = document.getElementById('menu-toggle-styles');
        if (style) style.remove();
        
        this.initialized = false;
    }
}

document.addEventListener('DOMContentLoaded', () => {
    new MenuToggle();
});

Tag: tag1

  • April 16, 2025
    Welcome to WordPress. This is your first post. Edit or delete it, then start writing!