Esta es la tercera y última parte de nuestra serie de tutoriales Crear el carrusel perfecto. En la parte 1, evaluamos los carruseles en Netflix y Amazon, dos de los carruseles más utilizados del mundo. Instalamos nuestro carrusel e implementamos touch scroll..
Luego, en la parte 2, agregamos el desplazamiento horizontal del mouse, la paginación y un indicador de progreso. Auge.
Ahora, en nuestra parte final, vamos a investigar el mundo turbio y a menudo olvidado de la accesibilidad del teclado. Ajustaremos nuestro código para volver a medir el carrusel cuando cambie el tamaño de la ventana gráfica. Y, por último, haremos algunos toques finales utilizando la física de primavera..
Puedes continuar donde lo dejamos con este CodePen.
Es cierto que la mayoría de los usuarios no dependen de la navegación con el teclado, por lo que, lamentablemente, a veces nos olvidamos de los usuarios que sí lo hacen. En algunos países, dejar un sitio web inaccesible puede ser ilegal. Pero peor, es un movimiento de pene..
La buena noticia es que generalmente es fácil de implementar. De hecho, los navegadores hacen la mayor parte del trabajo por nosotros. En serio: intenta hojear el carrusel que hemos hecho. Porque hemos usado el marcado semántico, ya puedes!
Excepto, te darás cuenta, nuestros botones de navegación desaparecen. Esto se debe a que el navegador no permite enfocar un elemento fuera de nuestra ventana gráfica. Así que a pesar de que tenemos desbordamiento: oculto
conjunto, no podemos desplazar la página horizontalmente; de lo contrario, la página se desplazará para mostrar el elemento enfocado..
Esto está bien, y calificaría, en mi opinión, como "reparable", aunque no exactamente encantador.
El carrusel de Netflix también funciona de esta manera. Pero como la mayoría de sus títulos están cargados perezosamente, y también son pasivamente accesibles desde el teclado (lo que significa que no han escrito ningún código específicamente para manejarlo), no podemos seleccionar ningún título más allá de los pocos que hemos escrito. ya cargado. También se ve terrible:
Podemos hacerlo mejor.
atención
EventoPara ello, vamos a escuchar el atención
Evento que dispara sobre cualquier elemento del carrusel. Cuando un elemento recibe el foco, lo vamos a consultar por su posición. Entonces, lo comprobaremos contra sliderX
y sliderVisibleWidth
para ver si ese elemento está dentro de la ventana visible. Si no lo es, lo paginaremos usando el mismo código que escribimos en la parte 2.
Al final de carrusel
función, añadir este detector de eventos:
slider.addEventListener ('focus', onFocus, true);
Notarás que hemos proporcionado un tercer parámetro., cierto
. En lugar de agregar un detector de eventos a cada elemento, podemos usar lo que se conoce como delegación de eventos para escuchar eventos en un solo elemento, su padre directo. los atención
el evento no burbujea, entonces cierto
está diciendo al oyente del evento que escuche la capturar etapa, la etapa donde el evento se dispara en cada elemento de la ventana
hasta el objetivo (en este caso, el elemento que recibe el foco).
Por encima de nuestro creciente bloque de oyentes de eventos, agregue el enfocado
función:
función onFocus (e)
Estaremos trabajando en esta función por el resto de esta sección..
Necesitamos medir el artículo izquierda
y Correcto
compensar y verificar si alguno de los puntos se encuentra fuera del área actualmente visible.
El artículo es proporcionado por el evento objetivo
parámetro, y podemos medirlo con getBoundingClientRect
:
const left, right = e.target.getBoundingClientRect ();
izquierda
y Correcto
son relativos a la vista, no el control deslizante Así que tenemos que conseguir el contenedor del carrusel izquierda
compensación para tener en cuenta para eso. En nuestro ejemplo, esto será 0
, pero para hacer que el carrusel sea robusto, debe ser colocado en cualquier lugar.
const carouselLeft = container.getBoundingClientRect (). left;
Ahora, podemos hacer una simple comprobación para ver si el elemento está fuera del área visible del control deslizante y paginar en esa dirección:
si < carouselLeft) gotoPrev(); else if (right > carouselLeft + sliderVisibleWidth) gotoNext ();
Ahora, cuando pasamos por ahí, ¡el carrusel se mueve con confianza con nuestro enfoque del teclado! Solo unas pocas líneas de código para mostrar más amor a nuestros usuarios..
Es posible que haya notado al seguir este tutorial que si cambia el tamaño de la ventana de visualización del navegador, el carrusel ya no se pagina correctamente. Esto se debe a que medimos su ancho en relación con su área visible solo una vez, en el momento de la inicialización..
Para asegurarnos de que nuestro carrusel se comporta correctamente, debemos reemplazar parte de nuestro código de medición con un nuevo detector de eventos que se activa cuando ventana
redimensiona.
Ahora, cerca del comienzo de tu carrusel
Función, justo después de la línea donde definimos. barra de progreso
, queremos reemplazar tres de estos const
mediciones con dejar
, porque los vamos a cambiar cuando cambie la ventana gráfica:
const totalItemsWidth = getTotalItemsWidth (items); const maxXOffset = 0; deja minXOffset = 0; deja sliderVisibleWidth = 0; vamos a clampXOffset;
Luego, podemos mover la lógica que previamente calculó estos valores a una nueva medida de carrusel
función:
function measureCarousel () sliderVisibleWidth = slider.offsetWidth; minXOffset = - (totalItemsWidth - sliderVisibleWidth); clampXOffset = pinza (minXOffset, maxXOffset);
Queremos invocar inmediatamente esta función, por lo que aún establecemos estos valores en la inicialización. En la siguiente línea, llame medida de carrusel
:
measureCarousel ();
El carrusel debería funcionar exactamente como antes. Para actualizar el tamaño de la ventana, simplemente agregamos este detector de eventos al final de nuestro carrusel
función:
window.addEventListener ('resize', measureCarousel);
Ahora, si cambia el tamaño del carrusel y trata de paginar, seguirá funcionando como se esperaba..
Vale la pena considerar que, en el mundo real, es posible que tenga varios carruseles en la misma página, multiplicando el impacto en el rendimiento de este código de medición por esa cantidad.
Como explicamos brevemente en la parte 2, no es prudente realizar cálculos pesados más a menudo de lo que debe. Con los eventos de puntero y desplazamiento, dijimos que desea realizarlos una vez por fotograma para ayudar a mantener 60 fps. Los eventos de cambio de tamaño son un poco diferentes, ya que todo el documento tendrá un nuevo flujo, probablemente el momento más intensivo de recursos que una página web encontrará..
No necesitamos volver a medir el carrusel hasta que el usuario haya terminado de cambiar el tamaño de la ventana, porque mientras tanto no interactuarán con él. Podemos envolver nuestro medida de carrusel
función en una función especial llamada rebotar.
Una función de rebote esencialmente dice: "Solo active esta función cuando no se haya llamado en over X
milisegundos. "Puede leer más sobre la publicación de rebotes en el excelente manual de David Walsh, y también puede obtener un código de ejemplo..
Hasta ahora, hemos creado un carrusel bastante bueno. Es accesible, anima muy bien, funciona con el tacto y el mouse, y proporciona una gran flexibilidad de diseño de una manera que los carruseles de desplazamiento nativo no permiten.
Pero esta no es la serie de tutoriales "Crear un carrusel bastante bueno". Es hora de que nos mostremos un poco, y para hacer eso, tenemos un arma secreta. muelles.
Vamos a agregar dos interacciones usando resortes. Uno para el tacto, y otro para la paginación. Ambos le harán saber al usuario, de una manera divertida y lúdica, que han llegado al final del carrusel..
Primero, agreguemos un tirón estilo iOS cuando un usuario intenta desplazar el control deslizante más allá de sus límites. Actualmente, estamos limitando el desplazamiento táctil usando clampXOffset
. En su lugar, reemplazemos esto con algún código que aplique un tirón cuando el desplazamiento calculado está fuera de sus límites.
Primero, necesitamos importar nuestra primavera. Hay un transformador llamado muelles no lineales
que aplica una fuerza exponencialmente creciente contra el número que le proporcionamos, hacia una origen
. Lo que significa que cuanto más jalemos el control deslizante, más tirará hacia atrás. Podemos importarlo así:
const applyOffset, clamp, nonlinearSpring, pipe = transform;
En el determinarDragDirection
Función, tenemos este código:
action.output (pipe ((x) => x, applyOffset (action.x.get (), sliderX.get ()), clampXOffset, (v) => sliderX.set (v)));
Justo arriba, creemos nuestros dos resortes, uno para cada límite de desplazamiento del carrusel:
elasticidad const = 5; const tugLeft = nonlinearSpring (elasticity, maxXOffset); const tugRight = nonlinearSpring (elasticity, minXOffset);
Decidir sobre un valor para elasticidad
Es una cuestión de jugar y ver lo que se siente bien. Un número demasiado bajo, y el resorte se siente demasiado rígido. Demasiado alto y no notará su tirón, o peor, empujará el deslizador aún más lejos del dedo del usuario!
Ahora solo necesitamos escribir una función simple que aplique uno de estos resortes si el valor suministrado está fuera del rango permitido:
const applySpring = (v) => if (v> maxXOffset) devolver tugLeft (v); si < minXOffset) return tugRight(v); return v; ;
Podemos reemplazar clampXOffset
en el código de arriba con applySpring
. Ahora, si coloca el control deslizante más allá de sus límites, lo arrastrará hacia atrás!
Sin embargo, cuando dejamos la primavera, se encaja de nuevo sin ceremonias en su lugar. Queremos modificar nuestra stopTouchScroll
función, que actualmente maneja el desplazamiento momentum, para verificar si el control deslizante todavía está fuera del rango permitido y, si es así, aplicar un resorte con el física
acción en cambio.
los física
La acción es capaz de modelar resortes, también. Solo necesitamos proporcionarlo primavera
y a
propiedades.
En stopTouchScroll
, mover el pergamino existente física
Inicialización de una lógica que garantiza que estamos dentro de los límites de desplazamiento:
const currentX = sliderX.get (); si (currentX < minXOffset || currentX > maxXOffset) else action = physics (from: currentX, speed: sliderX.getVelocity (), friction: 0.2). output (pipe (clampXOffset, (v) => sliderX.set (v))). start ();
Dentro de la primera cláusula del Si
declaración, sabemos que el control deslizante está fuera de los límites de desplazamiento, por lo que podemos agregar nuestro resorte:
action = physics (from: currentX, to: (currentX < minXOffset) ? minXOffset : maxXOffset, spring: 800, friction: 0.92 ).output((v) => sliderX.set (v)) .start ();
Queremos crear un resorte que se sienta ágil y sensible. He elegido un relativamente alto primavera
valor para tener un apretado "pop", y he bajado el fricción
a 0.92
Para permitir un pequeño rebote. Podrías poner esto a 1
Para eliminar el rebote por completo..
Como un poco de tarea, trata de reemplazar la clampXOffset
en el salida
función del pergamino física
con una función que activa un resorte similar cuando el desplazamiento x alcanza sus límites. En lugar de la parada abrupta actual, intente hacerlo rebotar suavemente al final.
Los usuarios táctiles siempre obtienen la bondad de la primavera, ¿verdad? Compartamos ese amor con los usuarios de escritorio detectando cuando el carrusel está en sus límites de desplazamiento y dándole un tirón indicativo para mostrar al usuario de manera clara y confiable que está al final..
Primero, queremos deshabilitar los botones de paginación cuando se alcanza el límite. Primero agreguemos una regla CSS que estilice los botones para mostrar que están discapacitado
. En el botón
regla, añadir:
transición: fondo 200ms lineales; & .disabled background: #eee;
Estamos usando una clase aquí en lugar de la más semántica. discapacitado
atributo porque todavía queremos capturar eventos de clic, lo que, como su nombre lo indica, discapacitado
bloquearía.
Agrega esto discapacitado
clase al botón Prev, porque cada carrusel comienza la vida con un 0
compensar:
Hacia la cima de carrusel
, hacer una nueva función llamada checkNavButtonStatus
. Queremos que esta función simplemente verifique el valor proporcionado contra MinXOffset
y maxXOffset
y configurar el botón discapacitado
clase en consecuencia:
función checkNavButtonStatus (x) si (x <= minXOffset) nextButton.classList.add('disabled'); else nextButton.classList.remove('disabled'); if (x >= maxXOffset) prevButton.classList.add ('deshabilitado'); else prevButton.classList.remove ('disabled');
Sería tentador llamar esto cada vez. sliderX
cambios Si lo hiciéramos, los botones comenzaban a parpadear cada vez que un resorte oscilaba alrededor de los límites del desplazamiento. También conduciría a raro comportamiento si uno de los botones fue presionado durante una de esas animaciones de primavera. El tirón del "final de desplazamiento" siempre debe dispararse si estamos al final del carrusel, incluso si hay una animación de primavera alejándola del extremo absoluto.
Así que tenemos que ser más selectivos sobre cuándo llamar a esta función. Parece sensato llamarlo:
En la última línea de la en la rueda
, añadir checkNavButtonStatus (newX);
.
En la última línea de ir
, añadir checkNavButtonStatus (targetX);
.
Y finalmente, al final de determinarDragDirection
, y en la cláusula de desplazamiento de impulso (el código dentro del más
) de stopTouchScroll
, reemplazar:
(v) => sliderX.set (v)
Con:
(v) => sliderX.set (v); checkNavButtonStatus (v);
Ahora todo lo que queda es enmendar gotoPrev
y gotoSiguiente
para comprobar la lista de clases de su botón disparador discapacitado
y solo paginar si está ausente:
const gotoNext = (e) =>! e.target.classList.contains ('disabled')? goto (1): notifyEnd (-1, maxXOffset); const gotoPrev = (e) =>! e.target.classList.contains ('disabled')? goto (-1): notifyEnd (1, minXOffset);
los notificar
la función es solo otra física
Primavera, y se ve así:
function notifyEnd (delta, targetOffset) if (action) action.stop (); action = physics (from: sliderX.get (), to: targetOffset, velocidad: 2000 * delta, spring: 300, friction: 0.9) .output ((v) => sliderX.set (v)) .start ( );
Juega con eso, y otra vez, modifica la física
params a tu gusto.
Solo queda un pequeño error. Cuando el control deslizante se desplaza más allá de su límite izquierdo, la barra de progreso se está invirtiendo. Podemos arreglarlo rápidamente reemplazando:
progressBarRenderer.set ('scaleX', progress);
Con:
progressBarRenderer.set ('scaleX', Math.max (progress, 0));
Nosotros podríaevita que rebote hacia el otro lado, pero personalmente creo que es muy bueno que refleje el movimiento del resorte. Solo se ve extraño cuando se voltea de adentro hacia afuera.
Con las aplicaciones de una sola página, los sitios web duran más tiempo en la sesión de un usuario. A menudo, incluso cuando cambia la "página", todavía estamos ejecutando el mismo tiempo de ejecución JS que en la carga inicial. No podemos confiar en una pizarra limpia cada vez que el usuario hace clic en un enlace, y eso significa que tenemos que limpiarnos para evitar que los oyentes de eventos activen elementos muertos..
En React, este código se coloca en el componenteWillLeave
método. Usos de vue antesDestroy
. Esta es una implementación JS pura, pero aún podemos proporcionar un método de destrucción que funcionaría por igual en cualquiera de los dos marcos..
Hasta ahora, nuestro carrusel
La función no ha devuelto nada. Vamos a cambiar eso.
Primero, cambia la línea final, la línea que llama. carrusel
, a:
const destroyCarousel = carrusel (document.querySelector ('. container'));
Vamos a devolver sólo una cosa de carrusel
, Una función que desenreda a todos nuestros oyentes de eventos. Al final de la carrusel
función, escribir:
return () => container.removeEventListener ('touchstart', startTouchScroll); container.removeEventListener ('wheel', onWheel); nextButton.removeEventListener ('click', gotoNext); prevButton.removeEventListener ('click', gotoPrev); slider.removeEventListener ('focus', onFocus); window.removeEventListener ('resize', measureCarousel); ;
Ahora si llamas destruye
Y tratar de jugar con el carrusel, ¡no pasa nada! Es casi un poco triste verlo así..
Uf. ¡Eso fue mucho! Cuán lejos hemos llegado. Puedes ver el producto terminado en este CodePen. En esta parte final, hemos agregado la accesibilidad del teclado, volviendo a medir el carrusel cuando cambia la ventana gráfica, algunas adiciones divertidas con la física de primavera y el paso desgarrador pero necesario de volver a arrancarlo todo.
Espero que hayas disfrutado este tutorial tanto como yo disfruté escribiéndolo. Me encantaría escuchar sus opiniones sobre otras formas en que podríamos mejorar la accesibilidad o agregar más toques divertidos..