Cómo compilar un componente de navegación lateral

Descripción general básica de cómo crear una navegación lateral desplegable responsiva

En esta publicación, quiero compartir contigo cómo creé un prototipo de un componente de barra lateral de navegación para la Web que es responsivo, tiene estado, admite la navegación con el teclado, funciona con JavaScript y sin él, y es compatible con todos los navegadores. Prueba la demostración.

Si prefieres un video, aquí tienes una versión de este artículo en YouTube:

Descripción general

Es difícil crear un sistema de navegación responsivo. Algunos usuarios usarán un teclado, otros tendrán computadoras de escritorio potentes y otros visitarán el sitio desde un dispositivo móvil pequeño. Todos los visitantes deben poder abrir y cerrar el menú.

Demostración del diseño adaptable de escritorio a dispositivos móviles
Tema claro y oscuro en iOS y Android

Tácticas web

En esta exploración de componentes, tuve el placer de combinar algunas funciones críticas de la plataforma web:

  1. CSS :target
  2. Cuadrícula de CSS
  3. Transformaciones de CSS
  4. Consultas de medios CSS para el viewport y las preferencias del usuario
  5. JS para focus Mejoras en la UX

Mi solución tiene una barra lateral y se activa solo cuando se encuentra en un viewport "móvil" de 540px o menos. 540px será nuestro punto de interrupción para alternar entre el diseño interactivo para dispositivos móviles y el diseño estático para computadoras.

Pseudoclase :target de CSS

Un vínculo <a> establece el hash de la URL en #sidenav-open y el otro en vacío (''). Por último, un elemento tiene el id para que coincida con el hash:

<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<aside id="sidenav-open">
  …
</aside>

Cuando haces clic en cada uno de estos vínculos, cambia el estado de hash de la URL de nuestra página. Luego, con una seudoclase, muestro y oculto la navegación lateral:

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

Cuadrícula de CSS

En el pasado, solo usaba diseños y componentes de navegación lateral con posición absoluta o fija. Sin embargo, la cuadrícula, con su sintaxis grid-area, nos permite asignar varios elementos a la misma fila o columna.

Pilas

El elemento de diseño principal #sidenav-container es una cuadrícula que crea 1 fila y 2 columnas, 1 de cada una se llama stack. Cuando el espacio es limitado, CSS asigna todos los elementos secundarios del elemento <main> al mismo nombre de cuadrícula, lo que coloca todos los elementos en el mismo espacio y crea una pila.

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

El <aside> es el elemento de animación que contiene la navegación lateral. Tiene 2 elementos secundarios: el contenedor de navegación <nav> llamado [nav] y un fondo <a> llamado [escape], que se usa para cerrar el menú.

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

Ajusta 2fr y 1fr para encontrar la proporción que te guste para la superposición del menú y su botón de cierre de espacio negativo.

Una demostración de lo que sucede cuando cambias la proporción.

Transiciones y transformaciones en 3D de CSS

Nuestro diseño ahora se apila en un tamaño de viewport para dispositivos móviles. Hasta que agregue algunos estilos nuevos, se superpondrá a nuestro artículo de forma predeterminada. En esta próxima sección, quiero lograr la siguiente UX:

  • Animar la apertura y el cierre
  • Solo se debe animar con movimiento si el usuario lo acepta.
  • Anima visibility para que el enfoque del teclado no ingrese al elemento fuera de la pantalla

Cuando comience a implementar animaciones de movimiento, quiero tener en cuenta la accesibilidad desde el principio.

Movimiento accesible

No todos querrán una experiencia de movimiento deslizable. En nuestra solución, esta preferencia se aplica ajustando una variable de CSS --duration dentro de una consulta de medios. Este valor de la consulta de medios representa la preferencia del sistema operativo del usuario por el movimiento (si está disponible).

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
Una demostración de la interacción con y sin la duración aplicada.

Ahora, cuando nuestro sidenav se desliza para abrirse y cerrarse, si un usuario prefiere un movimiento reducido, muevo instantáneamente el elemento a la vista, manteniendo el estado sin movimiento.

Transición, transformación y traducción

Panel lateral hacia afuera (predeterminado)

Para establecer el estado predeterminado de nuestro sidenav en dispositivos móviles como un estado fuera de la pantalla, posiciono el elemento con transform: translateX(-110vw).

Ten en cuenta que agregué otro 10vw al código fuera de pantalla típico de -100vw para asegurarme de que el box-shadow de la navegación lateral no se asome a la ventana principal cuando esté oculta.

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);
  }
}
Navegación lateral en

Cuando el elemento #sidenav coincide con :target, establece la posición translateX() en la posición base 0 y observa cómo CSS desliza el elemento desde su posición externa de -110vw hasta su posición "interna" de 0 durante var(--duration) cuando se cambia el hash de la URL.

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

Visibilidad de la transición

El objetivo ahora es ocultar el menú de los lectores de pantalla cuando no esté visible, de modo que los sistemas no enfoquen un menú fuera de la pantalla. Para lograr esto, establezco una transición de visibilidad cuando cambia :target.

  • Cuando ingreses, no hagas una transición de visibilidad; sé visible de inmediato para que pueda ver el elemento deslizarse y aceptar el enfoque.
  • Cuando sale, la visibilidad de la transición se retrasa, por lo que cambia a hidden al final de la transición de salida.

Mejoras en la UX de accesibilidad

Esta solución se basa en cambiar la URL para que se pueda administrar el estado. Naturalmente, aquí se debe usar el elemento <a>, que obtiene algunas funciones de accesibilidad agradables de forma gratuita. Decoremos nuestros elementos interactivos con etiquetas que articulen claramente la intención.

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
  <svg>...</svg>
</a>
Una demostración de la UX de interacción con VoiceOver y el teclado.

Ahora, nuestros botones de interacción principales indican claramente su intención tanto para el mouse como para el teclado.

:is(:hover, :focus)

Este práctico pseudoselector funcional de CSS nos permite ser inclusivos rápidamente con nuestros estilos de desplazamiento, ya que también los comparte con el enfoque.

.hamburger:is(:hover, :focus) svg > line {
  stroke: hsl(var(--brandHSL));
}

Agrega JavaScript

Presiona escape para cerrar.

La tecla Escape del teclado debería cerrar el menú, ¿verdad? Conectemos eso.

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
Historial del navegador

Para evitar que la interacción de abrir y cerrar apile varias entradas en el historial del navegador, agrega el siguiente código JavaScript intercalado al botón de cierre:

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

Esto quitará la entrada del historial de URLs cuando se cierre, como si el menú nunca se hubiera abierto.

UX de enfoque

El siguiente fragmento nos ayuda a enfocar los botones de abrir y cerrar después de que se abren o cierran. Quiero que sea fácil activar o desactivar la opción.

sidenav.addEventListener('transitionend', e => {
  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
      ? document.querySelector('#sidenav-close').focus()
      : document.querySelector('#sidenav-button').focus();
})

Cuando se abra la navegación lateral, enfoca el botón de cierre. Cuando se cierre la barra de navegación lateral, enfoca el botón de apertura. Para ello, llamo a focus() en el elemento en JavaScript.

Conclusión

Ahora que sabes cómo lo hice, ¿cómo lo harías tú? Esto genera una arquitectura de componentes divertida. ¿Quién creará la primera versión con ranuras? 🙂

Diversifiquemos nuestros enfoques y aprendamos todas las formas de crear en la Web. Crea un Glitch, envíame un tweet con tu versión y la agregaré a la sección de Remixes de la comunidad que se encuentra más abajo.

Remixes de la comunidad