Apple es conocida por las elegantes animaciones en sus páginas de productos. Por ejemplo, a medida que se desplaza hacia abajo en la página, los productos pueden aparecer, las MacBooks se abren y los iPhones giran, todo mientras muestra el hardware, muestra el software y cuenta historias interactivas sobre cómo se utilizan los productos.
Solo mira este video de la experiencia web móvil para el iPad Pro:
Fuente: Gorjeo
Muchos de los efectos que ves allí no se crean solo en HTML y CSS. ¿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 <canvas>
elemento.
Echemos un vistazo en profundidad a uno de estos efectos para ver cómo se hace para que pueda 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 basico
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 complejas de WebGL ni bibliotecas avanzadas de JavaScript.
Al sincronizar cada cuadro con la posición de desplazamiento del usuario, podemos reproducir la animación mientras 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 <canvas>
elemento que controlamos con JavaScript dándole una ID.
En CSS, le daremos a nuestro documento una altura de 100vh y haremos nuestro <body>
5⨉ más alto que eso para darnos la longitud de desplazamiento necesaria para que esto funcione. También combinaremos el color de fondo del documento con el color de fondo de nuestras imágenes.
Lo último que haremos es colocar el <canvas>
, céntrelo y limite el max-width
y height
por lo que no excede 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 nuestro <canvas>
permanece en la parte superior de la ventana gráfica. Eso es todo el HTML y CSS que necesitamos.
Pasemos a cargar las imágenes.
Obteniendo las imágenes correctas
Como 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, basado en 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 entero, necesitaremos convertirlo en una cadena y usar padStart(4, '0')
anteponer ceros delante de nuestro índice hasta que alcancemos cuatro dígitos para que coincidan con los nombres de nuestros archivos. Entonces, por ejemplo, pasar 1 a esta función devolverá 0001.
Eso nos da una forma de manejar las rutas de las imágenes. Aquí está la primera imagen de la secuencia dibujada en el <canvas>
elemento:
Como puede ver, la primera imagen está en la página. En este punto, es solo un archivo estático. Lo que queremos es actualizarlo en función de la posición de desplazamiento del usuario. Y no solo queremos cargar un archivo de imagen y luego intercambiarlo cargando otro archivo de imagen. Queremos dibujar las imágenes en el <canvas>
y actualice el dibujo con la siguiente imagen de la secuencia (pero llegaremos a eso en un momento).
Ya hicimos 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. Haremos un detector de eventos para rastrear eso y manejaremos algunas matemáticas para calcular qué imagen cargar.
Necesitamos saber:
- Donde 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 scrollTop
para obtener la posición de desplazamiento vertical del elemento, que en nuestro caso es la parte superior del documento. Eso 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 scrollTop
valor por el valor máximo que el usuario puede desplazarse 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 se corresponda con la secuencia de numeración de imágenes para que podamos devolver la imagen correcta para esa posición. Podemos hacer esto multiplicando el número de progreso por el número de cuadros (imágenes) que tenemos. Usaremos Math.floor()
para redondear ese número y envolverlo en Math.min()
con nuestro recuento máximo de fotogramas para que nunca supere 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
Ahora sabemos qué imagen debemos dibujar a medida que cambia el progreso de desplazamiento del usuario. Aquí es donde entra en juego la magia de
Una de esas características es un método llamado requestAnimationFrame
que funciona con el navegador para actualizar <canvas>
de una manera que no podríamos hacer si estuviéramos trabajando con archivos de imagen directa. Por eso fui con un <canvas>
enfoque en lugar de, digamos, un <img>
elemento o un <div>
con una imagen de fondo.
requestAnimationFrame
coincidirá con la frecuencia de actualización del navegador y permitirá la aceleración de hardware mediante el uso de WebGL para representarlo mediante la tarjeta de video del dispositivo o los gráficos integrados. En otras palabras, obtendremos transiciones súper suaves entre cuadros, ¡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. requestAnimationFrame
toma 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 el <canvas>
:
requestAnimationFrame(() => updateImage(frameIndex + 1))
Estamos subiendo el frameIndex
por 1 porque, mientras que la secuencia de imágenes comienza en 0001.jpg, nuestro cálculo de progreso de desplazamiento comienza en realidad en 0. Esto asegura que los dos valores estén siempre 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 frameIndex
en la función. Eso establece la fuente de la imagen con la siguiente imagen de la secuencia, que se dibuja en nuestro <canvas>
elemento.
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 da como resultado un pequeño desfase entre los fotogramas de la imagen. Eso es porque cada nueva imagen envía una nueva solicitud de red, que requiere una nueva descarga.
Deberíamos intentar precargar las imágenes nuevas solicitudes de red. De esa manera, cada cuadro ya está descargado, lo que hace que las transiciones sean mucho más rápidas y que la animación sea mucho más suave.
Todo lo que tenemos que hacer es recorrer toda la secuencia 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 hábil, también incluye muchas imágenes. 148 para ser exactos.
No importa mucho que optimicemos las imágenes, o cuán rápido sea el CDN que las atiende, cargar cientos de imágenes siempre resultará en una página hinchada. 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 ajustados, pero no podemos decir lo mismo de los usuarios sin esos lujos. Es un equilibrio difícil de lograr, pero debemos tener en cuenta la experiencia de todos y cómo nuestras decisiones los afectan.
Algunas cosas que podemos hacer para ayudar a lograr ese equilibrio incluyen:
- Cargando una sola imagen de respaldo en lugar de la secuencia de imágenes completa
- Creación de secuencias que utilizan archivos de imagen más pequeños para ciertos dispositivos.
- Permitir al usuario habilitar la secuencia, quizás 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, oye, las estadísticas de rendimiento comienzan a verse mucho mejor:
Sí, todavía es una página pesada. Pero es mucho más ligero de lo que obtendríamos sin ninguna consideración de rendimiento. Así es como Apple puede obtener 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 llevan a través de los conceptos básicos de la generación de animaciones con After Effects y, al mismo tiempo, brindan una manera fácil de incluirlas en proyectos.