Crea el carrusel perfecto, parte 2

Bienvenido de nuevo a la serie de tutoriales Crear el carrusel perfecto. Estamos haciendo un carrusel encantador y accesible utilizando JavaScript y las capacidades de física, interpolación y seguimiento de entrada de Popmotion..

En la parte 1 de nuestro tutorial, observamos cómo Amazon y Netflix crearon sus carruseles y evaluaron las ventajas y desventajas de sus enfoques. Con nuestros aprendizajes, decidimos una estrategia para nuestro carrusel e implementamos el desplazamiento táctil utilizando la física.

En la parte 2, vamos a implementar el desplazamiento horizontal del mouse. También vamos a ver algunas técnicas comunes de paginación e implementar una. Finalmente, vamos a conectar una barra de progreso que indicará qué tan lejos está el usuario a través del carrusel..

Puedes restaurar tu punto de guardado abriendo este CodePen, que continúa donde lo dejamos.

Desplazamiento horizontal del ratón

Es raro que un carrusel de JavaScript respete el desplazamiento horizontal del mouse. Esto es una vergüenza: en computadoras portátiles y ratones que implementan el desplazamiento horizontal basado en el impulso, esta es, con mucho, la forma más rápida de navegar por el carrusel. Es tan malo como forzar a los usuarios táctiles a navegar a través de botones en lugar de deslizar.

Afortunadamente, se puede implementar en solo unas pocas líneas de código. Al final de tu carrusel función, agregar un nuevo detector de eventos:

container.addEventListener ('wheel', onWheel);

Debajo de tu startTouchScroll evento, agregar una función de código auxiliar llamada en la rueda:

función onWheel (e) console.log (e.deltaX)

Ahora, si pasa la rueda de desplazamiento sobre el carrusel y verifica el panel de la consola, verá la distancia de la rueda en la salida del eje x.

Al igual que con el tacto, si el movimiento de la rueda es mayormente vertical, la página debería desplazarse como de costumbre. Si es horizontal, queremos capturar el movimiento de la rueda y aplicarlo al carrusel. Entonces, en en la rueda, Reemplace la console.log con:

const angle = calc.angle (x: e.deltaX, y: e.deltaY); if (angleIsVertical (angle)) retorno; e.stopPropagation (); e.preventDefault ();

Este bloque de código detendrá el desplazamiento de página si el desplazamiento es horizontal. Actualizar el desplazamiento x de nuestro deslizador ahora es solo una cuestión de tomar el evento deltaX propiedad y añadiendo que a nuestro actual sliderX valor:

const newX = clampXOffset (sliderX.get () + - e.deltaX); sliderX.set (newX);

Estamos reutilizando nuestro anterior. clampXOffset función para ajustar este cálculo y asegurarse de que el carrusel no se desplace más allá de sus límites medidos.

Eventos de desplazamiento de desplazamiento aparte

Cualquier buen tutorial que se ocupe de los eventos de entrada explicará lo importante que es acelerar esos eventos. Esto se debe a que los eventos de desplazamiento, mouse y toque pueden dispararse más rápido que la velocidad de cuadros del dispositivo.

No desea realizar un trabajo innecesario que requiera un uso intensivo de recursos, como renderizar el carrusel dos veces en un cuadro, ya que es un desperdicio de recursos y una forma rápida de crear una interfaz de sensación lenta..

Este tutorial no se ha referido a eso porque los procesadores proporcionados por Popmotion implementan Framesync, un pequeño programador de trabajos sincronizado con cuadros. Esto significa que podrías llamar (v) => sliderRenderer.set ('x', v) varias veces seguidas, y la representación costosa solo ocurriría una vez, en el siguiente cuadro.

Paginación

Eso es el desplazamiento terminado. Ahora necesitamos inyectar algo de vida en los botones de navegación hasta ahora no amados.

Ahora, este tutorial trata sobre la interacción, así que siéntase libre de diseñar estos botones como desee. Personalmente, encuentro las flechas de dirección más intuitivas (¡y completamente internacionalizadas de forma predeterminada!).

Cómo debería funcionar la paginación?

Hay dos estrategias claras que podríamos tomar al paginar el carrusel: punto por puntoprimer objeto oscurecido. Solo hay una estrategia correcta pero, como he visto la otra implementada tan a menudo, pensé que valdría la pena explicar por qué es incorrecto.

1. Artículo por Artículo

Simplemente mida el desplazamiento x del siguiente elemento de la lista y anime el estante esa cantidad. Es un algoritmo muy simple que asumo que se escoge por su simplicidad en lugar de por su facilidad de uso..

El problema es que la mayoría de las pantallas pueden mostrar muchos elementos a la vez, y la gente los escaneará todos antes de intentar navegar..

Se siente lento, si no totalmente frustrante. La única situación en la que esta sería una buena opción es si saber los elementos en su carrusel son del mismo ancho o solo un poco más pequeños que el área visible.

Sin embargo, si estamos viendo varios elementos, es mejor que utilicemos el primer método de elementos ocultos:

2. Primer artículo obscurecido

Este método simplemente busca la primer objeto oscurecido en la dirección que queramos mover el carrusel, toma su desplazamiento x, y luego se desplaza a ese.

Al hacerlo, obtenemos la cantidad máxima de elementos nuevos que funcionan bajo el supuesto de que el usuario ha visto a todos los presentes en la actualidad..

Debido a que estamos incorporando más elementos, el carrusel requiere menos clics para navegar. Una navegación más rápida aumentará el compromiso y asegurará que sus usuarios vean más de sus productos.

Oyentes del evento

Primero, configuremos nuestros oyentes de eventos para que podamos comenzar a jugar con la paginación.

Primero debemos seleccionar nuestros botones anterior y siguiente. En el parte superior del carrusel función, añadir:

const nextButton = container.querySelector ('. next'); const prevButton = container.querySelector ('. prev');

Entonces, en el fondo del carrusel función, añadir los oyentes de eventos:

nextButton.addEventListener ('clic', gotoNext); prevButton.addEventListener ('clic', gotoPrev);

Finalmente, justo encima de su bloque de oyentes de eventos, agregue las funciones reales:

función goto (delta)  const gotoNext = () => goto (1); const gotoPrev = () => goto (-1);

ir Es la función que va a manejar toda la lógica para la paginación. Simplemente toma un número que representa la dirección de viaje que deseamos paginar.. gotoSiguientegotoPrev simplemente llame a esta función con 1-1, respectivamente.

Cálculo de una "página"

Un usuario puede desplazarse libremente por este carrusel, y hay norte artículos dentro de ella, y el carrusel podría ser redimensionado. Entonces, el concepto de una página tradicional no es directamente aplicable aquí. No vamos a contar el número de páginas..

En cambio, cuando el ir Se llama función, vamos a mirar en la dirección de delta y encontrar el primer elemento parcialmente oscurecido. Ese será el primer elemento en nuestra próxima "página".

El primer paso es obtener el desplazamiento x actual de nuestro control deslizante, y utilizarlo con el ancho visible total del control deslizante para calcular un desplazamiento "ideal" al que nos gustaría desplazarnos. La compensación ideal es lo que nosotros haría Desplácese hasta si fuéramos ingenuos al contenido del control deslizante. Nos proporciona un buen lugar para comenzar a buscar nuestro primer artículo..

const currentX = sliderX.get (); deja targetX = currentX + (- sliderVisibleWidth * delta);

Podemos utilizar una optimización descarada aquí. Proporcionando nuestra targetX al clampXOffset función que hicimos en el tutorial anterior, podemos ver si su salida es diferente a targetX. Si lo es, significa nuestra targetX está fuera de nuestros límites desplazables, por lo que no necesitamos averiguar el elemento más cercano. Simplemente nos desplazamos hasta el final.

const clampedX = clampXOffset (targetX); targetX = (targetX === clampedX)? findClosestItemOffset (targetX, delta): clampedX;

Encontrar el artículo más cercano

Es importante tener en cuenta que el siguiente código funciona en el supuesto de que Todos los artículos en tu carrusel son del mismo tamaño.. Bajo esa suposición, podemos hacer optimizaciones como no tener que medir el tamaño de cada artículo. Si tus articulos son Diferentes tamaños, esto seguirá siendo un buen punto de partida.. 

Encima de tu ir función, añadir el findClosestItemOffset Función referenciada en el último fragmento:

función findClosestItem (targetX, delta) 

Primero, necesitamos saber qué tan amplios son nuestros artículos y el espacio entre ellos. los Element.getBoundingClientRect () El método puede proporcionar toda la información que necesitamos. por anchura, simplemente medimos el primer elemento del elemento. Para calcular el espaciado entre elementos, podemos medir el Correcto desplazamiento del primer elemento y el izquierda desplaza el segundo, y luego resta el primero del segundo: 

const right, width = items [0] .getBoundingClientRect (); const spacing = items [1] .getBoundingClientRect (). left - right;

Ahora, con el targetXdelta variables que pasamos a la función, tenemos todos los datos que necesitamos para calcular rápidamente un desplazamiento para desplazarnos hasta.

El cálculo es dividir el absoluto. targetX valor por el ancho + espacio. Esto nos dará la cantidad exacta de elementos que podemos colocar dentro de esa distancia.

const totalItems = Math.abs (targetX) / (ancho + espaciado);

Luego, redondee hacia arriba o hacia abajo dependiendo de la dirección de paginación (nuestra delta). Esto nos dará el número de completar artículos que podemos ajustar.

const totalCompleteItems = delta === 1? Math.floor (totalItems): Math.ceil (totalItems);

Finalmente, multiplica ese número por ancho + espacio para darnos un color compensado con un elemento completo.

return 0 - totalCompleteItems * (ancho + espaciado);

Animar la paginación

Ahora que tenemos nuestro targetX Calculado, podemos animarlo a ello! Para esto, vamos a utilizar el caballo de batalla de la animación web, el entre.

Para los no iniciados, "tween" es la abreviatura de serentre Una interpolación cambia de un valor a otro, durante un período de tiempo establecido. Si has usado transiciones CSS, esto es lo mismo.. 

Hay una serie de beneficios (y deficiencias) en el uso de JavaScript sobre CSS para interpolaciones. En este caso, porque también estamos animando. sliderX Con la física y los comentarios de los usuarios, nos será más fácil permanecer en este flujo de trabajo para la interpolación.

También significa que más adelante podemos conectar una barra de progreso y funcionará naturalmente con todas nuestras animaciones, de forma gratuita..

Primero queremos importar entre de Popmotion:

const calc, css, easing, physics, pointer, transform, tween, value = window.popmotion;

Al final de nuestro ir función, podemos añadir nuestra interpolación que anima desde corrienteXtargetX:

tween (from: currentX, to: targetX, onUpdate: sliderX). start ();

Por defecto, Popmotion establece duración300 milisegundos y facilitareasing.easeOut. Estos han sido seleccionados específicamente para brindar una sensación de respuesta a las animaciones que responden a las sugerencias del usuario, pero siéntase libre de jugar y ver si encuentra algo que se adapte mejor a la sensación de su marca..

Indicador de progreso

Es útil para los usuarios tener alguna indicación sobre dónde están en el carrusel. Para ello, podemos conectar un indicador de progreso..

Su barra de progreso puede ser estilizada de varias maneras. Para este tutorial, hemos creado un div coloreado, 5px alto, que se ejecuta entre los botones anterior y siguiente. Es la forma en que conectamos esto con nuestro código y animamos la barra lo que es importante y es el enfoque de este tutorial.

Aún no has visto el indicador porque originalmente lo diseñamos con transformar: escalaX (0). Usamos un escala transformar para ajustar el ancho de la barra porque, como explicamos en la parte 1, las transformaciones son más eficaces que cambiar propiedades como izquierda o, en este caso, anchura.

También nos permite escribir fácilmente un código que establece la escala como un porcentaje: el valor actual de sliderX Entre MinXOffset y maxXOffset.

Empecemos por seleccionar nuestro div.progress-bar tras nuestro Botón anterior selector:

const progressBar = container.querySelector ('. progress-bar');

Después de que definamos sliderRenderer, podemos agregar un renderizador para barra de progreso:

const progressBarRenderer = css (progressBar);

Ahora vamos a definir una función para actualizar el escalaX de la barra de progreso.

Usaremos un calc función llamada getProgressFromValue. Esto toma un rango, en nuestro caso minmaxXOffset, y un tercer número. Devuelve el Progreso, un numero entre 01, de ese tercer número dentro del rango dado.

función updateProgressBar (x) const progress = calc.getProgressFromValue (maxXOffset, minXOffset, x); progressBarRenderer.set ('scaleX', progress); 

Hemos escrito el rango aquí como maxXOffset, minXOffset Cuando, intuitivamente, se debe revertir. Esto es porque X es un valor negativo, y maxXOffset También es un valor negativo mientras que MinXOffset es 0. los 0 es técnicamente el más grande de los dos números, pero el valor más pequeño en realidad representa el desplazamiento máximo. Negativos, eh?

Queremos que el indicador de progreso se actualice en forma sincronizada con sliderX, Así que vamos a cambiar esta línea:

const sliderX = value (0, (x) => sliderRenderer.set ('x', x));

A esta línea:

const sliderX = value (0, (x) => updateProgressBar (x); sliderRenderer.set ('x', x););

Ahora, cuando sliderX actualizaciones, igual que la barra de progreso.

Conclusión

Eso es todo por esta entrega! Puede tomar el último código en este CodePen. Hemos introducido con éxito el desplazamiento horizontal de las ruedas, la paginación y una barra de progreso..

¡El carrusel está en muy buena forma hasta ahora! En la entrega final, vamos a ir un paso más allá. Haremos que el carrusel sea completamente accesible para asegurar que cualquiera pueda usarlo. 

También vamos a agregar un par de toques encantadores con un tirón accionado por resorte cuando un usuario intenta desplazar el carrusel más allá de sus límites mediante el desplazamiento táctil o la paginación. 

Hasta entonces!