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..
¿Qué se necesita para que un carrusel sea "perfecto"? Tiene que ser accesible por:
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.
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
.
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.
Nuestra primera tarea es hacer que el carrusel se desplace. Hay dos maneras en que podríamos hacer esto:
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:
Alternativamente:
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..
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!
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');
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.
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!
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:
sliderX
.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;
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)));
¡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 0
a 1
, 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.
¡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.
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 foo
y bar
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. applyOffset
y abrazadera
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.!
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:
Esperamos verlos allí!