Cómo compilar un componente de navegación lateral

Descripción general fundamental de cómo crear una diapositiva responsiva fuera de la barra de navegación lateral

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

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

Descripción general

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

Demostración de diseño responsivo para computadoras de escritorio a dispositivos móviles
Tema claro y oscuro en iOS y Android

Tácticas web

En esta exploración de componentes, tuve la dicha de combinar algunas funciones esenciales de la plataforma web:

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

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

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 que debe coincidir 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, se cambia el estado de hash de la URL de nuestra página y, luego, con una seudoclase, muestro y oculta la barra de 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 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, 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;
  }
}

<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 el botón para cerrar el espacio negativo.

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

Transformaciones y transiciones CSS 3D

Ahora, nuestro diseño se apila en un tamaño de viewport para dispositivos móviles. Hasta que agregué nuevos estilos, se superpone a nuestro artículo de forma predeterminada. Estas son algunas UX que estoy buscando en la próxima sección:

  • Cómo animar la apertura y el cierre
  • Animar solo con movimiento si el usuario está de acuerdo con eso
  • Se anima visibility para que el enfoque del teclado no ingrese al elemento fuera de la pantalla

Cuando empiezo a implementar animaciones de movimiento, quiero empezar con la accesibilidad como prioridad.

Movimiento accesible

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

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

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

Ahora, cuando nuestra barra de navegación lateral se abre y se cierra, si un usuario prefiere un movimiento reducido, muevo inmediatamente el elemento a la vista, manteniendo el estado sin movimiento.

Transición, transformación y traducción

Navegación lateral hacia afuera (predeterminado)

Para establecer el estado predeterminado de nuestra navegación lateral en dispositivos móviles en un estado fuera de la pantalla, coloco el elemento con transform: translateX(-110vw).

Ten en cuenta que se agregó otro 10vw al código típico fuera de pantalla de -100vw para garantizar que el box-shadow de la barra de navegación lateral no se vea 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);
  }
}
Navegación lateral en

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

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

Mejoras en la UX de accesibilidad

Esta solución se basa en cambiar la URL para que se administre el estado. Naturalmente, aquí se debería usar el elemento <a>, y se obtienen algunas funciones de accesibilidad útiles de forma gratuita. Adornaremos nuestros elementos interactivos con etiquetas que indiquen 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 tanto para el mouse como para el teclado.

:is(:hover, :focus)

Este práctico pseudoselector funcional de CSS nos permite ser inclusivos con rapidez con nuestros estilos de desplazamiento compartiéndolos con el foco.

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

Amplificación en JavaScript

Presiona escape para cerrar

¿La tecla Escape del teclado debería cerrar el menú correctamente? Vamos a conectar 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 apila 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>

Esta acción quitará la entrada del historial de URLs cuando se cierre, lo que lo hará como si el menú nunca se hubiera abierto.

Enfoque en la UX

El siguiente fragmento nos ayuda a enfocarnos en los botones de abrir y cerrar después de que se abren o se cierran. Quiero que activar o desactivar 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 la barra de navegación lateral, enfoca el botón de cierre. Cuando se cierre la barra de navegación lateral, enfoca el botón para abrir. Para ello, llamo a focus() en el elemento de JavaScript.

Conclusión

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

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

Remixes de la comunidad