Crear el carrusel perfecto, parte 1

Los carruseles son un elemento básico de los sitios de transmisión y comercio electrónico. Tanto Amazon como Netflix los utilizan como herramientas de navegación prominentes. En este tutorial, evaluaremos el diseño de interacción de ambos y utilizaremos nuestros hallazgos para implementar el carrusel perfecto.

En esta serie de tutoriales, también aprenderemos algunas funciones de Popmotion, un motor de movimiento de JavaScript. Ofrece herramientas de animación como preadolescentes (útiles para la paginación), seguimiento de punteros (para desplazamiento) y física de primavera (para nuestros deliciosos toques finales).

La Parte 1 evaluará cómo Amazon y Netflix han implementado el desplazamiento. Luego, implementaremos un carrusel que se puede desplazar a través del tacto..

Al final de esta serie, habremos implementado la rueda y el teclado táctil, la paginación, las barras de progreso, la navegación con el teclado y algunos pequeños toques con la física de resortes. También habremos sido expuestos a alguna composición funcional básica..

Perfecto?

¿Qué se necesita para que un carrusel sea "perfecto"? Tiene que ser accesible por:

  • Ratón: Debe ofrecer botones anteriores y siguientes que sean fáciles de hacer clic y que no oculten el contenido..
  • Toque: Debe seguir el dedo y luego desplazarse con el mismo impulso que cuando el dedo se levanta de la pantalla.
  • Rueda de desplazamiento: A menudo se pasa por alto, el Apple Magic Mouse y muchos trackpads para portátiles ofrecen un desplazamiento horizontal suave. Debemos utilizar esas capacidades!
  • Teclado: Muchos usuarios prefieren no hacerlo o no pueden usar un mouse para navegar. Es importante que hagamos accesible nuestro carrusel para que los usuarios también puedan usar nuestro producto.

Finalmente, iremos más allá de las cosas y haremos de esto una pieza de UX segura y encantadora haciendo que el carrusel responda de manera clara y visceral con la física de la primavera cuando el control deslizante haya llegado al final.

Preparar

Primero, obtengamos el HTML y el CSS necesarios para construir un carrusel rudimentario por medio de este CodePen. 

El Pen está configurado con Sass para preprocesar CSS y Babel para transpilar ES6 JavaScript. También he incluido Popmotion, al que se puede acceder con window.popmotion.

Si lo prefiere, puede copiar el código en un proyecto local, pero deberá asegurarse de que su entorno sea compatible con Sass y ES6. También necesitarás instalar Popmotion con npm instalar popmotion.

Creando un nuevo carrusel

En cualquier página dada, podríamos tener muchos carruseles. Así que necesitamos un método para encapsular el estado y la funcionalidad de cada uno.

Voy a usar un función de fábrica preferible a clase. Las funciones de fábrica evitan la necesidad de usar el a menudo confuso esta palabra clave y simplificará el código para los propósitos de este tutorial.

En su editor de JavaScript, agregue esta función simple:

carrusel de funciones (contenedor)  carrusel (document.querySelector ('. container'));

Agregaremos nuestro código específico de carrusel dentro de este carrusel función.

¿Cómo y por qué de desplazamiento?

Nuestra primera tarea es hacer que el carrusel se desplace. Hay dos maneras en que podríamos hacer esto:

Desplazamiento nativo del navegador

La solución obvia sería establecer overflow-x: scroll en el control deslizante. Esto permitiría el desplazamiento nativo en todos los navegadores, incluidos los dispositivos de rueda de ratón horizontales y táctiles.

Hay, sin embargo, inconvenientes de este enfoque:

  • El contenido fuera del contenedor no sería visible, lo que puede ser restrictivo para nuestro diseño.
  • También limita las maneras en que podemos usar animaciones para indicar que hemos llegado al final.
  • Los navegadores de escritorio tendrán una barra de desplazamiento horizontal fea (¡aunque accesible!).

Alternativamente:

Animar traducirX

También podríamos animar el carrusel. traducirX propiedad. Esto sería muy versátil ya que podríamos implementar exactamente el diseño que nos gusta.. traducirX También es muy performante, ya que a diferencia del CSS. izquierda propiedad que puede ser manejada por la GPU del dispositivo.

En el lado negativo, tendríamos que volver a implementar la funcionalidad de desplazamiento con JavaScript. Eso es más trabajo, más código..

¿Cómo se acercan los desplazamientos de Amazon y Netflix??

Tanto el carrusel de Amazon como el de Netflix hacen diferentes concesiones para abordar este problema.

Amazonas anima el carrusel izquierda propiedad cuando en modo "escritorio". Animando izquierda es una opción increíblemente mala, ya que cambiarla provoca un nuevo cálculo del diseño. Esto requiere una gran cantidad de CPU, y las máquinas más antiguas tendrán problemas para alcanzar los 60 fps.

Quien haya tomado la decisión de animar. izquierda en lugar de traducirX debe ser un verdadero idiota (alerón Fui yo, en 2012. No estábamos tan iluminados en esos días.

Cuando detecta un dispositivo táctil, el carrusel utiliza el desplazamiento nativo del navegador. El problema con solo habilitar esto en el modo "móvil" es que los usuarios de escritorio con ruedas de desplazamiento horizontal se pierden. También significa que cualquier contenido fuera del carrusel deberá ser cortado visualmente:

Netflix anima correctamente el carrusel traducirX Propiedad, y lo hace en todos los dispositivos. Esto les permite tener un diseño que sangra fuera del carrusel:

Esto, a su vez, les permite hacer un diseño elegante donde los elementos se agrandan fuera de los bordes x e y del carrusel y los elementos circundantes se mueven fuera de su camino:

Desafortunadamente, la reimplementación de Netflix del desplazamiento para dispositivos táctiles no es satisfactoria: utiliza un sistema de paginación basado en gestos que se siente lento y molesto. Tampoco hay consideración para las ruedas de desplazamiento horizontal.

Podemos hacerlo mejor. Vamos a codificar!

Desplazarse como un profesional

Nuestro primer movimiento es agarrar el .deslizador nodo. Mientras estamos en ello, agarremos los elementos que contiene para poder averiguar la dimensión del control deslizante.

carrusel de funciones (contenedor) const slider = container.querySelector ('. slider'); const items = slider.querySelectorAll ('. item'); 

Midiendo el carrusel

Podemos calcular el área visible del control deslizante midiendo su ancho:

const sliderVisibleWidth = slider.offsetWidth;

También desearemos el ancho total de todos los elementos que contiene. Para mantener nuestro carrusel función relativamente limpia, pongamos este cálculo en una función separada en la parte superior de nuestro archivo.

Mediante el uso getBoundingClientRect para medir el izquierda desplazamiento de nuestro primer artículo y la Correcto compensando nuestro último artículo, podemos usar la diferencia entre ellos para encontrar el ancho total de todos los artículos.

function getTotalItemsWidth (items) const left = items [0] .getBoundingClientRect (); const right = items [items.length - 1] .getBoundingClientRect (); vuelta derecha - izquierda 

Tras nuestro sliderVisibleWidth medida, escriba:

const totalItemsWidth = getTotalItemsWidth (items);

Ahora podemos calcular la distancia máxima a la que se debe permitir a nuestro carrusel desplazarse. Es el ancho total de todos nuestros artículos., menos Un ancho completo de nuestro deslizador visible. Esto proporciona un número que permite que el elemento más a la derecha se alinee con el derecho de nuestro control deslizante:

const maxXOffset = 0; const minXOffset = - (totalItemsWidth - sliderVisibleWidth);

Con estas medidas en su lugar, estamos listos para comenzar a desplazar nuestro carrusel.

Ajuste traducirX

Popmotion viene con un procesador de CSS para la configuración simple y eficaz de las propiedades de CSS. También viene con una función de valor que se puede usar para rastrear números y, lo que es más importante (como veremos pronto), para consultar su velocidad.

En la parte superior de su archivo JavaScript, impórtelos así:

const css, value = window.popmotion;

Entonces, en la línea después de establecer MinXOffset, crear un procesador de CSS para nuestro control deslizante:

const sliderRenderer = css (control deslizante);

Y crear un valor para rastrear el desplazamiento x de nuestro deslizador y actualizar el deslizador traducirX propiedad cuando cambia:

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

Ahora, mover el control deslizante horizontalmente es tan simple como escribir:

sliderX.set (-100);

Intentalo!

Desplazamiento táctil

Queremos que nuestro carrusel comience a desplazarse cuando usuarioarrastra el control deslizante horizontalmente y para detener el desplazamiento cuando un usuario deja de tocar la pantalla. Nuestros manejadores de eventos se verán así:

deja actuar función stopTouchScroll () document.removeEventListener ('touchend', stopTouchScroll);  function startTouchScroll (e) document.addEventListener ('touchend', stopTouchScroll);  slider.addEventListener ('touchstart', startTouchScroll, passive: false);

En nuestro startTouchScroll función, queremos:

  • Detener cualquier otra acción de alimentación. sliderX.
  • Encuentra el punto de contacto de origen.
  • Escuchar la proxima tocar evento para ver si el usuario está arrastrando vertical u horizontalmente.

Después document.addEventListener, añadir:

if (action) action.stop ();

Esto detendrá cualquier otra acción (como el desplazamiento de impulso impulsado por la física que implementaremos en stopTouchScroll) de mover el control deslizante. Esto permitirá al usuario "capturar" inmediatamente el control deslizante si se desplaza más allá de un elemento o título en el que desea hacer clic..

A continuación, necesitamos almacenar el punto de contacto de origen. Eso nos permitirá ver dónde el usuario mueve su dedo a continuación. Si es un movimiento vertical, permitiremos el desplazamiento de la página como de costumbre. Si es un movimiento horizontal, desplazaremos el control deslizante en su lugar..

Queremos compartir esto TouchOrigin entre los manejadores de eventos. Así que después deja actuar añadir:

vamos a touchOrigin = ;

De vuelta en nuestro startTouchScroll controlador, añadir:

const touch = e.touches [0]; touchOrigin = x: touch.pageX, y: touch.pageY;

Ahora podemos agregar un tocar oyente del evento documento para determinar la dirección de arrastre en base a esto TouchOrigin:

document.addEventListener ('touchmove', determineDragDirection);

Nuestro determinarDragDirection La función medirá la siguiente ubicación táctil, verificará que realmente se haya movido y, si es así, mida el ángulo para determinar si es vertical u horizontal:

function determinDragDirection (e) const touch = e.changedTouches [0]; const touchLocation = x: touch.pageX, y: touch.pageY; 

Popmotion incluye algunas calculadoras útiles para medir cosas como la distancia entre dos coordenadas x / y. Podemos importar aquellos como este:

const calc, css, value = window.popmotion;

Luego, medir la distancia entre los dos puntos es cuestión de usar el distancia calculadora:

const distance = calc.distance (touchOrigin, touchLocation);

Ahora si el toque se ha movido, podemos desactivar este detector de eventos..

si (! distancia) regreso; document.removeEventListener ('touchmove', determineDragDirection);

Mida el ángulo entre los dos puntos con la ángulo calculadora:

const angle = calc.angle (touchOrigin, touchLocation);

Podemos usar esto para determinar si este ángulo es un ángulo horizontal o vertical, pasándolo a la siguiente función. Agregue esta función al principio de nuestro archivo:

función angleIsVertical (angle) const isUp = (angle <= -90 + 45 && angle >= -90 - 45); const isDown = (ángulo <= 90 + 45 && angle >= 90 - 45); return (isUp || isDown); 

Esta función devuelve cierto si el ángulo provisto está dentro de -90 +/- 45 grados (recto hacia arriba) o 90 +/- 45 grados (hacia abajo). Así que podemos agregar otro regreso cláusula si esta función devuelve cierto.

if (angleIsVertical (angle)) retorno;

Seguimiento de puntero

Ahora que sabemos que el usuario está intentando desplazar el carrusel, podemos comenzar a rastrear su dedo. Popmotion ofrece una acción de puntero que emitirá las coordenadas x / y de un mouse o puntero táctil.

Primero, importar puntero:

const calc, css, puntero, valor = window.popmotion;

Para rastrear la entrada táctil, proporcione el evento de origen a puntero:

acción = puntero (e) .start ();

Queremos medir la inicial. X Posición de nuestro puntero y aplicar cualquier movimiento al control deslizante. Para eso, podemos usar un transformador llamado applyOffset.

Los transformadores son funciones puras que toman un valor y lo devuelven, sí, transformado. Por ejemplo: const doble = (v) => v * 2.

const calc, css, puntero, transformar, valor = window.popmotion; const applyOffset = transformar;

applyOffset Es una función al curry. Esto significa que cuando lo llamamos, crea una nueva función que luego se le puede pasar un valor. Primero lo llamamos con un número del que queremos medir el desplazamiento, en este caso el valor actual de action.x, y un número para aplicar ese desplazamiento a. En este caso, esa es nuestra. sliderX.

Entonces nuestro applyOffset La función se verá así:

const applyPointerMovement = applyOffset (action.x.get (), sliderX.get ());

Ahora podemos utilizar esta función en el puntero. salida devolución de llamada para aplicar el movimiento del puntero al control deslizante.

action.output ((x) => slider.set (applyPointerMovement (x)));

Parando, Con Estilo

¡El carrusel ahora se puede arrastrar al tacto! Puedes probar esto usando la emulación de dispositivo en las Herramientas del desarrollador de Chrome.

Se siente un poco janky, ¿verdad? Es posible que haya encontrado un desplazamiento similar al anterior: levanta el dedo y el desplazamiento se detiene. O el desplazamiento se detiene y luego una pequeña animación se encarga de falsificar la continuación del desplazamiento..

No vamos a hacer eso. Podemos usar la acción física en Popmotion para tomar la verdadera velocidad de sliderX y aplicarle fricción durante un tiempo..

Primero, agréguelo a nuestra creciente lista de importaciones:

const calc, css, física, puntero, valor = window.popmotion;

Luego, al final de nuestra stopTouchScroll función, añadir:

if (action) action.stop (); acción = física (de: sliderX.get (), velocidad: sliderX.getVelocity (), fricción: 0.2) .output ((v) => sliderX.set (v)) .start ();

aquí, desde y velocidad se están configurando con el valor actual y la velocidad de sliderX. Esto garantiza que nuestra simulación física tenga las mismas condiciones de inicio iniciales que el movimiento de arrastre del usuario.

fricción se está estableciendo como 0.2. La fricción se establece como un valor de 01, con 0 no siendo fricción en absoluto y 1 siendo fricción absoluta. Intente jugar con este valor para ver el cambio que hace a la "sensación" del carrusel cuando un usuario deja de arrastrar..

Los números más pequeños lo harán sentir más liviano, y los números más grandes harán que el movimiento sea más pesado. Para un movimiento de desplazamiento, me siento 0.2 alcanza un buen equilibrio entre errático y lento.

Límites

¡Pero hay un problema! Si has estado jugando con tu nuevo carrusel táctil, es obvio. No hemos limitado el movimiento, por lo que es posible tirar literalmente su carrusel!

Hay otro transformador para este trabajo., abrazadera. Esta es también una función al curry, es decir, si la llamamos con un valor mínimo y máximo, digamos 0 y 1, devolverá una nueva función. En este ejemplo, la nueva función restringirá cualquier número que se le asigne entre 0 y 1:

abrazadera (0, 1) (5); // devuelve 1

Primero, importar abrazadera:

const applyOffset, clamp = transformar;

Queremos usar esta función de sujeción en nuestro carrusel, así que agregue esta línea después de que definamos MinXOffset:

const clampXOffset = clamp (minXOffset, maxXOffset);

Vamos a enmendar los dos. salida Hemos puesto en marcha nuestras acciones utilizando una composición ligera funcional con el tubo transformador.

Tubo

Cuando llamamos a una función, la escribimos así:

foo (0);

Si queremos darle la salida de esa función a otra función, podríamos escribirla así:

barra (foo (0));

Esto se vuelve un poco difícil de leer, y solo empeora a medida que agregamos más y más funciones.

Con tubo, Podemos componer una nueva función de foobar que podemos reutilizar

const foobar = pipe (foo, bar); foobar (0);

También está escrito en un inicio natural -> orden de acabado, lo que facilita su seguimiento. Podemos usar esto para componer. applyOffsetabrazadera en una sola función. Importar tubo:

const applyOffset, clamp, pipe = transformar;

Reemplace la salida devolución de llamada de nuestro puntero con:

pipe ((x) => x, applyOffset (action.x.get (), sliderX.get ()), clampXOffset, (v) => sliderX.set (v))

Y reemplazar el salida devolución de llamada de física con:

pipe (clampXOffset, (v) => sliderX.set (v))

Este tipo de composición funcional puede crear procesos descriptivos, paso a paso, a partir de funciones más pequeñas y reutilizables..

Ahora, cuando arrastra y tira el carrusel, no se moverá fuera de sus límites.

La parada abrupta no es muy satisfactoria. Pero eso es un problema para una parte posterior.!

Conclusión

Eso es todo para la parte 1. Hasta ahora, hemos echado un vistazo a los carruseles existentes para ver las fortalezas y debilidades de los diferentes enfoques de desplazamiento. Hemos utilizado el seguimiento de entrada de Popmotion y la física para animar de forma eficaz nuestro carrusel. traducirX con desplazamiento táctil. También hemos sido introducidos a la composición funcional y funciones al curry..

Puede obtener una versión comentada de la "historia hasta ahora" en este CodePen.

En las próximas entregas, veremos:

  • desplazando con la rueda del ratón
  • Volver a medir el carrusel cuando la ventana cambia de tamaño
  • Paginación, con accesibilidad de teclado y mouse.
  • Toques encantadores, con la ayuda de la física de primavera.

Esperamos verlos allí!