Hagamos una de esas elegantes animaciones de desplazamiento que se utilizan en las páginas de productos de Apple

Índice
  1. El concepto básico
  2. Comience con el marcado y los estilos.
  3. Obteniendo las imágenes correctas
  4. Conexión de imágenes al progreso de desplazamiento del usuario
  5. Actualizando el lienzo con la imagen correcta
  6. Aún mejor con la precarga de imágenes
  7. ¡Manifestación!
  8. Una nota rápida sobre el rendimiento

Apple es conocida por las elegantes animaciones en las páginas de sus productos. Por ejemplo, a medida que se desplaza hacia abajo en la página, los productos pueden deslizarse a la vista, las MacBooks se abren y los iPhone giran, todo mientras se muestra el hardware, se demuestra el software y se cuentan historias interactivas sobre cómo se utilizan los productos.

Simplemente mire este video de la experiencia web móvil para iPad Pro:

Muchos de los efectos que ves allí no se crean solo en HTML y CSS. ¿Y entonces qué?, preguntas. Bueno, puede ser un poco difícil de entender. Incluso el uso de DevTools del navegador no siempre revelará la respuesta, ya que a menudo no puede ver más allá de un canvaselemento.

Echemos un vistazo en profundidad a uno de estos efectos para ver cómo se hace y poder recrear algunos de estos efectos mágicos en nuestros propios proyectos. Específicamente, repliquemos la página del producto AirPods Pro y el efecto de luz cambiante en la imagen principal.

El concepto básico

La idea es crear una animación como una secuencia de imágenes en rápida sucesión. Ya sabes, ¡como un libro animado! No se necesitan escenas WebGL complejas ni bibliotecas JavaScript avanzadas.

Al sincronizar cada cuadro con la posición de desplazamiento del usuario, podemos reproducir la animación a medida que el usuario se desplaza hacia abajo (o hacia atrás) en la página.

Comience con el marcado y los estilos.

El HTML y CSS para este efecto es muy fácil ya que la magia ocurre dentro del canvaselemento que controlamos con JavaScript dándole una ID.

En CSS, le daremos a nuestro documento una altura de 100vh y haremos que nuestro body5⨉ sea más alto que eso para tener la longitud de desplazamiento necesaria para que esto funcione. También haremos coincidir el color de fondo del documento con el color de fondo de nuestras imágenes.

Lo último que haremos será posicionar canvas, centrar y limitar max-widthpara heightque no supere las dimensiones de la ventana gráfica.

html {  height: 100vh;}
body {  background: #000;  height: 500vh;}
canvas {  position: fixed;  left: 50%;  top: 50%;  max-height: 100vh;  max-width: 100vw;  transform: translate(-50%, -50%);}

En este momento, podemos desplazarnos hacia abajo en la página (aunque el contenido no exceda la altura de la ventana gráfica) y permanecemos canvasen la parte superior de la ventana gráfica. Ese es todo el HTML y CSS que necesitamos.

Pasemos a cargar las imágenes.

Obteniendo las imágenes correctas

Dado que trabajaremos con una secuencia de imágenes (nuevamente, como un libro animado), asumiremos que los nombres de los archivos están numerados secuencialmente en orden ascendente (es decir, 0001.jpg, 0002.jpg, 0003.jpg, etc.) en el mismo directorio.

Escribiremos una función que devuelva la ruta del archivo con el número del archivo de imagen que queremos, según la posición de desplazamiento del usuario.

const currentFrame = index = (  `https://www.apple.com/105/media/us/airpods-pro/2019/1299e2f5_9206_4470_b28e_08307a42f19b/anim/sequence/large/01-hero-lightpass/${index.toString().padStart(4, '0')}.jpg`)

Dado que el número de la imagen es un número entero, necesitaremos convertirlo en una cadena y usarlo padStart(4, '0')para anteponer ceros delante de nuestro índice hasta llegar a cuatro dígitos que coincidan con los nombres de nuestros archivos. Entonces, por ejemplo, pasar 1 a esta función devolverá 0001.

Eso nos da una manera de manejar las rutas de las imágenes. Aquí está la primera imagen de la secuencia dibujada en el canvaselemento:

Como puede ver, la primera imagen está en la página. En este punto, es sólo un archivo estático. Lo que queremos es actualizarlo en función de la posición de desplazamiento del usuario. Y no queremos simplemente cargar un archivo de imagen y luego intercambiarlo cargando otro archivo de imagen. Queremos dibujar las imágenes en canvasy actualizar el dibujo con la siguiente imagen de la secuencia (pero llegaremos a eso en un momento).

Ya creamos la función para generar la ruta del archivo de imagen en función del número que le pasamos, así que lo que debemos hacer ahora es rastrear la posición de desplazamiento del usuario y determinar el marco de imagen correspondiente para esa posición de desplazamiento.

Conexión de imágenes al progreso de desplazamiento del usuario

Para saber qué número debemos pasar (y por lo tanto qué imagen cargar) en la secuencia, debemos calcular el progreso de desplazamiento del usuario. Crearemos un detector de eventos para rastrearlo y manejaremos algunos cálculos para calcular qué imagen cargar.

Necesitamos saber:

  • Dónde comienza y termina el desplazamiento
  • El progreso de desplazamiento del usuario (es decir, un porcentaje de qué tan lejos está el usuario en la página)
  • La imagen que corresponde al progreso de desplazamiento del usuario.

Usaremos scrollToppara obtener la posición de desplazamiento vertical del elemento, que en nuestro caso resulta ser la parte superior del documento. Ese servirá como valor de punto de partida. Obtendremos el valor final (o máximo) restando la altura de la ventana de la altura de desplazamiento del documento. A partir de ahí, dividiremos el scrollTopvalor por el valor máximo que el usuario puede desplazar hacia abajo, lo que nos da el progreso de desplazamiento del usuario.

Luego, debemos convertir ese progreso de desplazamiento en un número de índice que corresponda con la secuencia de numeración de imágenes para que podamos devolver la imagen correcta para esa posición. Esto lo podemos hacer multiplicando el número de progreso por el número de fotogramas (imágenes) que tenemos. Usaremos Math.floor()para redondear ese número hacia abajo y ajustarlo Math.min()a nuestro recuento máximo de fotogramas para que nunca exceda el número total de fotogramas.

window.addEventListener('scroll', () = {    const scrollTop = html.scrollTop;  const maxScrollTop = html.scrollHeight - window.innerHeight;  const scrollFraction = scrollTop / maxScrollTop;  const frameIndex = Math.min(    frameCount - 1,    Math.floor(scrollFraction * frameCount)  );});

Actualizando el lienzo con la imagen correcta

Ahora sabemos qué imagen necesitamos dibujar a medida que cambia el progreso de desplazamiento del usuario. Aquí es donde entra en juego la magia del lienzo. canvas¡Tiene muchas funciones interesantes para crear de todo, desde juegos y animaciones hasta diseñar generadores de maquetas y todo lo demás!

Una de esas características es un método llamado requestAnimationFrameque funciona con el navegador para actualizar canvasde una manera que no podríamos hacerlo si estuviéramos trabajando con archivos de imagen simples. Es por eso que elegí un canvasenfoque en lugar de, digamos, un imgelemento o una divimagen de fondo.

requestAnimationFramecoincidirá con la frecuencia de actualización del navegador y permitirá la aceleración del hardware mediante WebGL para renderizarlo usando la tarjeta de video del dispositivo o los gráficos integrados. En otras palabras, obtendremos transiciones súper suaves entre fotogramas, ¡sin destellos de imagen!

Llamemos a esta función en nuestro detector de eventos de desplazamiento para intercambiar imágenes a medida que el usuario se desplaza hacia arriba o hacia abajo en la página. requestAnimationFrametoma un argumento de devolución de llamada, por lo que pasaremos una función que actualizará la fuente de la imagen y dibujará la nueva imagen en canvas:

requestAnimationFrame(() = updateImage(frameIndex + 1))

Estamos aumentando frameIndexen 1 porque, si bien la secuencia de imágenes comienza en 0001.jpg, nuestro cálculo del progreso de desplazamiento comienza en realidad en 0. Esto garantiza que los dos valores siempre estén alineados.

La función de devolución de llamada que pasamos para actualizar la imagen se ve así:

const updateImage = index = {  img.src = currentFrame(index);  context.drawImage(img, 0, 0);}

Pasamos el frameIndexa la función. Eso establece la fuente de la imagen con la siguiente imagen de la secuencia, que se dibuja en nuestro canvaselemento.

Aún mejor con la precarga de imágenes

Técnicamente hemos terminado en este punto. ¡Pero vamos, podemos hacerlo mejor! Por ejemplo, desplazarse rápidamente produce un pequeño retraso entre los fotogramas de la imagen. Esto se debe a que cada nueva imagen envía una nueva solicitud de red, lo que requiere una nueva descarga.

Deberíamos intentar precargar las imágenes de nuevas solicitudes de red. De esa manera, cada fotograma ya está descargado, lo que hace que las transiciones sean mucho más rápidas y la animación mucho más fluida.

Todo lo que tenemos que hacer es recorrer la secuencia completa de imágenes y cargarlas:

const frameCount = 148;
const preloadImages = () = {  for (let i = 1; i  frameCount; i++) {    const img = new Image();    img.src = currentFrame(i);  }};
preloadImages();

¡Manifestación!

Una nota rápida sobre el rendimiento

Si bien este efecto es bastante ingenioso, también contiene muchas imágenes. 148 para ser exactos.

No importa cuánto optimicemos las imágenes o qué tan rápido sea el CDN que las entrega, cargar cientos de imágenes siempre resultará en una página inflada. Digamos que tenemos varias instancias de esto en la misma página. Podríamos obtener estadísticas de rendimiento como esta:

Eso podría estar bien para una conexión a Internet de alta velocidad sin límites de datos estrictos, pero no podemos decir lo mismo de los usuarios sin esos lujos. Es difícil lograr un equilibrio, pero debemos tener en cuenta la experiencia de cada uno y cómo les afectan nuestras decisiones.

Algunas cosas que podemos hacer para ayudar a lograr ese equilibrio incluyen:

  • Cargar una única imagen alternativa en lugar de toda la secuencia de imágenes
  • Crear secuencias que utilizan archivos de imágenes más pequeños para ciertos dispositivos
  • Permitir al usuario habilitar la secuencia, tal vez con un botón que inicia y detiene la secuencia.

Apple emplea la primera opción. Si carga la página de AirPods Pro en un dispositivo móvil conectado a una conexión 3G lenta y, bueno, las estadísticas de rendimiento comienzan a verse mucho mejor:

Sí, todavía es una página pesada. Pero es mucho más liviano de lo que obtendríamos sin ninguna consideración de rendimiento. Así es como Apple puede conseguir tantas secuencias complejas en una sola página.


Otras lecturas

Si está interesado en cómo se generan estas secuencias de imágenes, un buen lugar para comenzar es la biblioteca Lottie de AirBnB. Los documentos lo guían a través de los conceptos básicos de la generación de animaciones con After Effects y, al mismo tiempo, le brindan una manera fácil de incluirlas en proyectos.

SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion