Cómo compilar un componente de navegación lateral

Una descripción general fundamental de cómo crear un panel lateral deslizante responsivo

En esta publicación, quiero compartir contigo cómo prototipé un componente de Sidenav para la Web que es responsivo, con estado, admite la navegación con el teclado, funciona con y sin JavaScript, y funciona en todos los navegadores. Prueba la demostración.

Si prefieres ver un video, aquí tienes una versión de esta publicación en YouTube:

Descripción general

Es difícil crear un sistema de navegación responsivo. Algunos usuarios tendrán un teclado, algunos tendrán computadoras de escritorio potentes y otros accederán desde un dispositivo móvil pequeño. Todas las personas que visiten el sitio deben poder abrir y cerrar el menú.

Demo de diseño responsivo de computadora de escritorio a dispositivo móvil
Temas claros y oscuros en iOS y Android

Tácticas web

En esta exploración de componentes, tuve la alegría de combinar algunas funciones fundamentales de la plataforma web:

  1. CSS :target
  2. Cuadrícula de CSS
  3. Transformaciones de CSS
  4. Consultas de medios de CSS para la vista del puerto y la preferencia del usuario
  5. JS para focus mejoras de 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 inflexión para cambiar entre el diseño interactivo para dispositivos móviles y el diseño estático para computadoras de escritorio.

Pseudoclase :target de CSS

Un vínculo <a> establece el hash de 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>

Si haces clic en cada uno de estos vínculos, se cambia el estado de hash de la URL de nuestra página y, luego, con una seudoclase, muestro y oculto el panel 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 barra lateral de 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, y 1 de cada una se denomina stack. Cuando el espacio es limitado, CSS asigna todos los elementos secundarios del elemento <main> al mismo nombre de cuadrícula, 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;
  }
}

<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 deseas 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.

Transformaciones y transiciones en 3D del CSS

Nuestro diseño ahora está apilado 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. Esta es la UX a la que me dirijo en la siguiente sección:

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

Cuando comienzo a implementar animaciones de movimiento, quiero tener en cuenta la accesibilidad.

Movimiento accesible

No todos querrán una experiencia de movimiento deslizante. En nuestra solución, esta preferencia se aplica ajustando una variable CSS --duration dentro de una consulta de medios. Este valor de consulta de contenido multimedia representa la preferencia del sistema operativo de un usuario para 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 duración aplicada.

Ahora, cuando nuestro panel lateral se abre y se cierra, si un usuario prefiere reducir el movimiento, muevo el elemento a la vista de inmediato y mantengo el estado sin movimiento.

Transición, transformación y traducción

Menú lateral hacia afuera (predeterminado)

Para establecer el estado predeterminado de nuestro panel lateral en dispositivos móviles en 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 del panel lateral no mire en el viewport principal cuando esté oculto.

@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);
  }
}
Menú lateral dentro

Cuando el elemento #sidenav coincida como :target, establece la posición translateX() en la base 0 y observa cómo CSS desliza el elemento de su posición fuera de -110vw a su posición "dentro" de 0 sobre var(--duration) cuando se cambia el hash de 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 esté fuera, para que los sistemas no enfoquen un menú fuera de la pantalla. Para ello, configuro una transición de visibilidad cuando cambia :target.

  • Cuando entres, no hagas la transición de visibilidad; haz que sea visible de inmediato para que pueda ver cómo el elemento se desliza y acepta el enfoque.
  • Cuando salgas, realiza la transición de visibilidad, pero deténla para que cambie a hidden al final de la transición de salida.

Mejoras de UX de accesibilidad

Esta solución se basa en cambiar la URL para que se administre el estado. Por supuesto, se debe usar el elemento <a> aquí, y obtiene algunas funciones de accesibilidad útiles de forma gratuita. Adornemos 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 la voz en off y la interacción con el teclado.

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

:is(:hover, :focus)

Este práctico seudoselector funcional de CSS nos permite ser inclusivos con nuestros estilos de desplazamiento del mouse con rapidez, ya que también los compartimos 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ú, ¿no? 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 apertura y cierre 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, lo que hará que sea como si nunca se hubiera abierto el menú.

UX de enfoque

El siguiente fragmento nos ayuda a enfocarnos en los botones de abrir y cerrar después de que se abren o cierran. Quiero que el cambio sea fácil.

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 el panel lateral, enfoca el botón de cierre. Cuando se cierre el panel 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? Esto crea una arquitectura de componentes divertida. ¿Quién hará la primera versión con ranuras? 🙂

Diversifiquemos nuestros enfoques y aprendamos todas las formas de compilar en la Web. Crea un Glitch, tuitea tu versión y la agregaré a la sección Remixes de la comunidad que aparece a continuación.

Remixes de la comunidad