Tutorial WordPress: Hacer que el menú se convierta en botón hamburguesa antes de romper

·

·

Introducción

Este tutorial explica con todo lujo de detalles cómo hacer que el menú de un tema WordPress se convierta en un botón hamburguesa antes de romper —es decir, detectar cuándo los elementos del menú empiezan a ocupar más ancho del disponible y cambiar a la versión colapsada automáticamente—. Se ofrecen dos estrategias: una solución CSS (media queries) y una solución robusta con JavaScript que mide el ancho real del menú y activa el botón hamburguesa dinámicamente. Incluye HTML/PHP para insertar el botón, CSS para el diseño y JS para la detección, además de consideraciones de accesibilidad y mejoras opcionales.

Estrategias generales

Requisitos previos

1) Marcar el HTML/PHP del menú en tu tema

Añade un botón toggle para la versión hamburguesa y el contenedor de navegación. El ejemplo siguiente muestra la estructura típica para header.php o la plantilla correspondiente. El botón incluye atributos ARIA para accesibilidad.

lt!-- header.php (o plantilla parcial del menú) --gt
ltbutton class=menu-toggle id=menu-toggle aria-expanded=false aria-controls=site-navigationgt
  ltspan class=sr-onlygtAbrir menúlt/spangt
  ltspan class=hamburgergt
    ltspan class=bargtlt/spangt
    ltspan class=bargtlt/spangt
    ltspan class=bargtlt/spangt
  lt/spangt
lt/buttongt

ltnav id=site-navigation class=main-navigation role=navigationgt
  lt?php
    wp_nav_menu( array(
      theme_location =gt primary,
      menu_class =gt primary-menu,
      container =gt false
    ) )
  ?gt
lt/navgt

Notas

2) CSS básico: estilos del botón hamburguesa y comportamiento

A continuación un CSS base para crear la hamburguesa, ocultar/mostrar el menú y animar la apertura. Este CSS asume que la clase menu-collapsed en el ltbodygt o en el contenedor indica que se debe mostrar el toggle en lugar del menú horizontal.

/ Estilos base: menú horizontal en desktop /
.main-navigation { display: block }
.primary-menu { display: flex gap: 1rem list-style: none margin: 0 padding: 0 }
.primary-menu gt li { white-space: nowrap }

/ Botón hamburguesa - por defecto oculto en desktop /
.menu-toggle { display: none align-items: center gap: .5rem background: none border: 0 cursor: pointer padding: .5rem }
.menu-toggle .hamburger { display: inline-block width: 24px height: 18px position: relative }
.menu-toggle .bar { display: block height: 2px background: #111 margin: 4px 0 transition: transform .25s ease, opacity .2s ease }

/ Estado abierto (al añadir .menu-open al cuerpo o nav) /
body.menu-open .primary-menu { display: block position: absolute top: 60px right: 10px background: #fff box-shadow: 0 6px 18px rgba(0,0,0,.12) padding: 1rem border-radius: 6px }
body.menu-open .menu-toggle[aria-expanded=true] .bar:nth-child(1) { transform: translateY(6px) rotate(45deg) }
body.menu-open .menu-toggle[aria-expanded=true] .bar:nth-child(2) { opacity: 0 }
body.menu-open .menu-toggle[aria-expanded=true] .bar:nth-child(3) { transform: translateY(-6px) rotate(-45deg) }

/ Cuando el menú esté colapsado (convertido a hamburguesa) mostramos el botón y ocultamos la lista horizontal /
body.menu-collapsed .menu-toggle { display: inline-flex }
body.menu-collapsed .primary-menu { display: none }

/ Mobile por defecto: opcionalmente forzamos comportamiento /
@media (max-width: 768px) {
  .menu-toggle { display: inline-flex }
  .primary-menu { display: none }
}

Explicación breve

3) JavaScript: medir el ancho y activar el modo hamburguesa automáticamente

La parte clave para “antes de romper” es medir el ancho total de los ítems del menú y compararlo con el ancho disponible del contenedor. Si los ítems no caben, añadimos la clase menu-collapsed para mostrar la hamburguesa. Además, añadiremos la lógica para abrir/cerrar el menú, manejar Escape, clic fuera y resize con debounce.

// Archivo: menu-collapse.js
(function(){
  const NAV_SELECTOR = #site-navigation
  const MENU_SELECTOR = .primary-menu
  const TOGGLE_ID = menu-toggle
  const COLLAPSE_CLASS = menu-collapsed
  const OPEN_CLASS = menu-open
  const DEBOUNCE_MS = 120

  const nav = document.querySelector(NAV_SELECTOR)
  const menu = nav ? nav.querySelector(MENU_SELECTOR) : null
  const toggle = document.getElementById(TOGGLE_ID)

  if (! nav  ! menu  ! toggle) return

  // Suma los anchos de los 
  • visibles (incluyendo margen) function getMenuItemsWidth() { const items = menu.children let total = 0 for (let i = 0 i lt items.length i ) { const el = items[i] const style = window.getComputedStyle(el) const w = el.offsetWidth const marginLeft = parseFloat(style.marginLeft) 0 const marginRight = parseFloat(style.marginRight) 0 total = (w marginLeft marginRight) } return total } function getNavAvailableWidth() { // Disponemos de padding o elementos adyacentes en header, por eso usamos clientWidth del nav contenedor return nav.clientWidth } function shouldCollapse() { // Si la suma de items es mayor que el ancho disponible, colapsar. const itemsWidth = getMenuItemsWidth() const navWidth = getNavAvailableWidth() // Se puede añadir un margen de seguridad (por ejemplo 20px) para que colapse antes de rozar. const SAFETY_MARGIN = 24 return itemsWidth SAFETY_MARGIN gt navWidth } // Añade o quita la clase COLLAPSE_CLASS del body (puedes cambiar a nav.classList si prefieres) function applyCollapseClass(collapse) { if (collapse) document.body.classList.add(COLLAPSE_CLASS) else document.body.classList.remove(COLLAPSE_CLASS) } // Debounce sencillo function debounce(fn, wait) { let t return function() { clearTimeout(t) t = setTimeout(() =gt fn.apply(this, arguments), wait) } } // Lógica inicial y on resize function update() { try { const collapse = shouldCollapse() applyCollapseClass(collapse) // Asegurarnos de cerrar el menú si dejamos el modo hamburguesa if (!collapse) { document.body.classList.remove(OPEN_CLASS) toggle.setAttribute(aria-expanded,false) } } catch(e) { // fail silently console.error(menu collapse error, e) } } const debouncedUpdate = debounce(update, DEBOUNCE_MS) window.addEventListener(resize, debouncedUpdate) window.addEventListener(orientationchange, debouncedUpdate) // Observador de cambios en el menú (p. ej. cambios de texto, items añadidos) const mo = new MutationObserver(debouncedUpdate) mo.observe(menu, { childList: true, subtree: true, characterData: true }) // Toggle open/close toggle.addEventListener(click, function(e){ const expanded = this.getAttribute(aria-expanded) === true this.setAttribute(aria-expanded, String(!expanded)) document.body.classList.toggle(OPEN_CLASS) }) // Cerrar con Escape document.addEventListener(keydown, function(e){ if (e.key === Escape document.body.classList.contains(OPEN_CLASS)) { document.body.classList.remove(OPEN_CLASS) toggle.setAttribute(aria-expanded,false) toggle.focus() } }) // Cerrar al hacer clic fuera del menú cuando esté abierto document.addEventListener(click, function(e){ if (! document.body.classList.contains(OPEN_CLASS)) return const target = e.target if (target === toggle toggle.contains(target)) return if (nav.contains(target)) return // clic fuera document.body.classList.remove(OPEN_CLASS) toggle.setAttribute(aria-expanded,false) }) // Inicial // Esperamos a que las fuentes se carguen (importante si el ancho cambia por webfonts) if (document.fonts document.fonts.ready) { document.fonts.ready.then(update).catch(update) } else { // fallback window.addEventListener(load, update) setTimeout(update, 500) } })()
  • Comentarios sobre el JS

    4) Mejores prácticas de accesibilidad (A11Y)

    5) Variantes y mejoras opcionales

    1. Off-canvas completo: en vez de mostrar un dropdown, puedes desplazar el contenido y mostrar un panel lateral usando transform CSS y role=dialog con aria-modal.
    2. Medir anchos con resizeObserver del nav: si quieres reaccionar a cambios de tamaño del contenedor por layout, puedes usar ResizeObserver además de window.resize.
    3. Animaciones y rendimiento: evita animar propiedades que fuerzan reflow en grandes dispositivos usa transform y opacity para animaciones suaves.
    4. Evitar ruptura con textos largos: si los items del menú pueden ser muy largos, considera truncarlos con text-overflow: ellipsis de lo contrario, el JS detectará y colapsará cuando sea necesario.

    6) Ejemplo de media query CSS (fallback simple)

    Si prefieres no usar JavaScript, puedes usar una media query con un punto de quiebre calculado según el número típico de items y longitud de texto. Es menos flexible pero sirve en muchos casos.

    / Ejemplo simple: colapsar antes de 900px /
    @media (max-width: 900px) {
      body .primary-menu { display: none }
      body .menu-toggle { display: inline-flex }
    }
    

    7) Manejo de submenús

    Si tu menú tiene submenús desplegables, añade control para expandir submenús en versión colapsada: íconos botones para cada item que tenga children. Puedes usar CSS para ocultarlos y JS para alternar la clase is-open en cada li padre.

    // Toggle simple de submenú para versión colapsada
    document.addEventListener(click, function(e){
      const btn = e.target.closest(.submenu-toggle)
      if (!btn) return
      const li = btn.closest(li)
      li.classList.toggle(is-open)
    })
    

    8) Checklist final antes de publicar

    Conclusión

    La solución ideal para transformar el menú en un botón hamburguesa antes de que el contenido rompa combina estilos CSS accesibles con una detección JavaScript que mida el ancho real de los elementos del menú. Esto evita puntos de quiebre rígidos y ofrece una mejor experiencia en temas con menús dinámicos o textos variables. Implementa la estructura PHP/HTML, los estilos CSS y el script de medición y prueba extensamente en distintos escenarios y tamaños de fuente.

    Recursos útiles



    Leave a Reply

    Your email address will not be published. Required fields are marked *