¿Quieres saber cómo se hace esta scroll animation?
O esta otra?
O quizás esta?
Hola soy Ricardo y te voy a explicar al detalle esas animaciones de scroll que has visto.
Todas están hechas con el mismo recurso.
Aquí tienes disponible todo el código que he hecho:
See the Pen
Animate image sprite on scroll with GSAP – Fire by animaticss (@animaticss)
on CodePen.
See the Pen
Animate image sprite on scroll with GSAP – Santa Claus, Fire or Car – Debug by animaticss (@animaticss)
on CodePen.
He hecho 2 codepens en total, el primero es el ejemplo con la llama y el fondo negro que será el que veremos en detalle.
El otro codepen que he hecho tiene más ejemplos y tiene un código diferente para que puedas ver cómo funciona.
Vamos a ver cómo se hace paso a paso.
Lo primero:
¿En qué caso podrías usar esta scroll animation?
Pues podrías usarlo para mostrar un producto de tu web en 360 grados, una animación personalizada basada en imágenes o cualquier otra cosa que se te ocurra.
¡Es un recurso más a tu cartera!
Además esto tiene múltiples variantes, en este caso yo he generado una animación basada en el scroll de la web.
Pero por ejemplo, podrías hacer que la animación fuera en función de las coordenadas de tu ratón.
Lo importante es tener claro los conceptos usados en este ejercicio para después poder añadir o modificar otros comportamientos distintos en cualquier otra web.
Lo he hecho con la librería Timelinemax de greensock, aquí tienes un enlace a la librería para más información:
GSAP (Timelinemax)
Para jugar con el documento, he usado la librería jQuery, pero usa lo que prefieras.
El concepto que hay detrás de todo esto es:
Cuando el usuario hace scroll por la web, hay que obtener en qué dirección está navegando, y en función de la dirección en la que navega, animar una tira de imágenes moviéndola en una dirección o en otra.
Vamos a verlo.
Vayamos por partes, primero el HTML:
<div class="c-section">
<div class="c-section__col-left">
<div class="c-sprite">
<div class="c-sprite__img js-sprite-img"></div>
</div>
</div>
<div class="c-section__col-right">
<div class="c-section__content">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
In porta aliquet urna, vel lacinia eros.
Donec vel placerat ante. Duis at ipsum elementum, egestas leo sit amet, iaculis est.
Aenean ac eleifend sem. Donec iaculis vehicula nisi.
Morbi ultrices, sem nec sagittis ultrices, purus diam fringilla enim, et euismod neque nisi vel lectus.
Nunc semper arcu id mattis vulputate. Vestibulum suscipit vitae dui at pellentesque.
</div>
<div class="c-section__spacer"></div>
<div class="c-section__content">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
In porta aliquet urna, vel lacinia eros.
Donec vel placerat ante. Duis at ipsum elementum, egestas leo sit amet, iaculis est.
Aenean ac eleifend sem. Donec iaculis vehicula nisi.
Morbi ultrices, sem nec sagittis ultrices, purus diam fringilla enim, et euismod neque nisi vel lectus.
Nunc semper arcu id mattis vulputate. Vestibulum suscipit vitae dui at pellentesque.
</div>
<div class="c-section__spacer"></div>
<div class="c-section__content">
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
In porta aliquet urna, vel lacinia eros.
Donec vel placerat ante. Duis at ipsum elementum, egestas leo sit amet, iaculis est.
Aenean ac eleifend sem. Donec iaculis vehicula nisi.
Morbi ultrices, sem nec sagittis ultrices, purus diam fringilla enim, et euismod neque nisi vel lectus.
Nunc semper arcu id mattis vulputate. Vestibulum suscipit vitae dui at pellentesque.
</div>
</div>
</div>
Como podrás ver en el HTML, no hay nada misterioso.
Encontrarás un nodo que envuelve todo, el bloque c-section.
Este bloque contiene dos columnas a su vez, que son
c-section__col-left y c-section__col-right.
La columna derecha contiene diferentes textos, sin más.
La columna de la izquierda contiene la imagen para hacer la scroll animation.
Pero no es una imagen cualquiera.
Es una imagen sprite.
Con imagen de tipo sprite me refiero a que es una imagen que en su interior tiene diferentes frames.
Mira, viendo la imagen lo entenderás más rápido:
Esto es una imagen sprite.
Como puedes observar, la imagen se repite en el eje X, pero va variando.
Al mover de manera muy rápida de frame a frame, genera una animación que parece un vídeo.
De hecho, los vídeos funcionan así.
Si lo haces lo suficientemente rápido claro, si lo haces demasiado lento, no generará el impacto deseado.
¿Te suena la expresión frames por segundo? Pues tiene que ver con todo esto.
Aquí tienes un vídeo por si quieres indagar sobre este tema:
Mira este mismo recurso usado en papel quedaría así:
Cada hoja de papel podríamos decir que es un frame de la animación, al pasar las hojas de manera rápida, se crea la animación.
Otra imagen de tipo sprite podría ser esta (está recortada para que no se vea demasiado pequeña):
Si dividiéramos la imagen por frames, quedaría así (está recortada para que no se vea demasiado pequeña):
Aquí puedes identificar mejor los frames que hay (imagen sin recortar):
Hay 16 frames en total.
Si te fijas, verás que cada frame varía un poco respecto el anterior, pero no demasiado.
Lo que haremos en la web, será mostrar uno solo de esos frames de la imagen y de manera muy rápida, cambiar el frame por el siguiente, consecutivamente.
Esto lo haremos con la librería timelinemax.
En este pequeño fragmento de vídeo, te muestro cómo se ve normal la animación de sprites y con el truco destapado (en el mismo vídeo):
El cuadrado verde del vídeo representa lo que verá el usuario.
El resto de la imagen estará oculta.
Si te fijas, va cambiando la posición de la imagen.
El valor en el código para el background-position es negativo (es generado por el JS, después lo veremos), esto es porque tenemos que mover la imagen en la dirección opuesta para poder ir pasando de un frame a otro.
Es decir esta imagen tiene los frames a su derecha, en ese caso tenemos que ir moviendo la imagen hacia la izquierda para ir pasando frames.
Eso es todo, no tiene más misterio.
Aunque para darle más juego al asunto, la animación sólo se aplicará mientras el usuario hace scroll.
Vale retomando el análisis de código, pasemos al CSS:
Verás que hay unas variables de tamaño para el frame en el código (SCSS).
$frame__w: 98px;
$frame__h: 250px;
Esto lo usaremos para saber cuánto mide cada frame de la imagen de sprite, para crear el lienzo visible donde sólo se verá un frame cada vez.
Quiero decir, el cuadrado de borde verde del vídeo de antes (tamaño del recorte).
En el CSS, también verás varios mixins para las media queries y estilos genéricos.
@mixin sm() {
@media (min-width: 501px) {
@content;
}
}
@mixin md() {
@media (min-width: 1101px) {
@content;
}
}
*,
*::before,
*::after{
box-sizing: border-box;
}
html,
body{}
body{
background: $black;
font-family: $font-a;
}
Después verás los estilos para el bloque c-section.
.c-section{
max-width: 100%;
width: 100%;
display: block;
padding: 100px 20px;
margin: 0 auto;
position: relative;
@include sm{
max-width: 100%;
width: 1140px;
padding: 200px 40px;
display: flex;
align-items: flex-start;
}
&__col-left{
width: 100%;
padding: 10px;
position: relative;
top: 0px;
@include sm{
width: 40%;
position: sticky;
top: 100px;
}
}
&__col-right{
width: 100%;
padding: 10px;
@include sm{
width: 60%;
}
}
&__content{
color: $white;
width: 100%;
display: block;
font-size: 16px;
line-height: 22px;
@include md{
font-size: 32px;
line-height: 42px;
}
}
&__spacer{
display: block;
width: 100%;
padding-bottom: 400px;
}
}
Tenemos en la columna izquierda la propiedad position sticky, para que nos siga por su contenedor padre y poder apreciar la animación realizada del sprite.
Si no conoces del todo la posición sticky de CSS, aquí tienes más información:
Sticky en CSS
También encontrarás el estilo para la imagen, c-sprite.
.c-sprite{
position: relative;
overflow: hidden;
width: $frame__w;
height: $frame__h;
display: block;
margin: 0 auto;
&__img{
width: $frame__w;
height: $frame__h; // HEIGHT FRAME
position: absolute;
top: 0;
left: 0;
max-width: none; // NO MAX WIDTH FOR SPRITE
background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/56901/sprite.png); // frames: 50, width: 98px, height: 250px.
}
}
Tenemos la imagen asignada por CSS a un div con la propiedad background-image.
Aunque podrías jugar también con un tag imagen en el HTML y moverla por CSS con la propiedad transform: translateX(); como en el otro codepen que he hecho.
Bien y ahora a por el JavaScript.
Lo primero, definimos algunas variables globales que necesitaremos.
Una variable bandera que usaremos para saber cuando lanzar la función de animación:
var ticking = false;
Una variable que se le asignará un setTimeout() para saber cuando acaba el scroll y lanzar otra función en ese caso:
var scrollEndCallback;
La última posición conocida de scroll en la web, la usaremos para saber la dirección del scroll del usuario.
var lastScrollTop = 0;
Necesitaremos saber cuantos steps o pasos hay en la imagen usada, que se calcula restando 1 al total de frames.
var steps = 49; // 50 - 1
Es decir, en la imagen hay un total de 50 frames (el ejemplo del fuego), pero solo necesitaremos 49 pasos para mostrar toda la animación ya que el primer frame ya se está visualizando.
Si recuerdas la imagen de antes, la de Santa Claus, tiene 16 frames en total.
El total de steps debería ser, el total de frames – 1, es decir: 15 steps.
Si multiplicamos 15 steps por 150px que es el ancho de cada frame, nos da un total de 2250px.
Queremos mover la imagen 2250px hacia la izquierda (-2250px), entonces, el valor debe ser negativo.
Después de este paso, volverá al primer punto y así, en bucle, recorrerá todos los frames.
- En la posición 0: 0px.
- En la posición 1: -150px.
- En la posición 2: -300px.
Vale sigamos entonces:
El ancho de cada frame (para el ejemplo del fuego):
var frame_w = 98;
El desplazamiento total que tendrá la imagen.
Para saber el desplazamiento total se hace calculando el total de steps por el ancho de cada frame:
var bg_position_total = steps * frame_w;
Nuestra línea de tiempo de la animación, creada a partir de la librería timelinemax ya mencionada.
// TIMELINE
var global_tl = new TimelineMax({ repeat:-1, paused: true, });
A este objeto le pasamos dos parámetros, repeat: -1; para que la animación sea infinita y paused: true, para que empiece pausada y activarla nosotros cuando hagamos scroll únicamente.
Ahora añadimos la animación que hará nuestra imagen al objeto global_tl.
Para hacerlo lo añadimos con el método .to.
global_tl.to( $(".js-sprite-img"), 1.3, {
backgroundPosition: "-" + bg_position_total + "px 0",
ease: SteppedEase.config(steps)
});
Este método espera los siguientes parámetros:
- Un nodo al que aplicar las animaciones ($(‘.js-sprite-img’)).
- El tiempo de duración de esa animación (1.3).
- Un objeto de las propiedades que deberá tener al final de dicha animación.
Le pasamos el nodo de la imagen y establecemos el tiempo total, por ejemplo en 1.3 segundos.
También, le pasamos un objeto con las propiedades finales que debe tener el nodo, en este caso lo que queremos es que la posición de la imagen de fondo cambie hasta la posición bg_position_total.
Como puedes observar el valor lo hemos puesto negativo, esto es por la explicación que hemos comentado antes.
Hay que mover la tira de imágenes hacia la izquierda para pasar a los siguientes frames.
Además solo hay que mover la imagen en el eje X, por eso en el eje Y siempre mantenemos el valor 0.
Retomando el análisis de código, también vemos en las propiedades del objeto la propiedad ease: SteppedEase.config(steps)
SteppedEase proporciona una manera fácil de definir un número específico de pasos que la transición debe tomar.
Es decir, el total que hemos pasado de distancia que debe recorrer la animación, lo divide entre tantos puntos como le digamos, en este caso el valor de la variable steps.
Por lo que internamente sabrá que los puntos que debe ir mostrando son:
- 0px 0px
- -98px 0px
- -186px 0px
- hasta llegar al final, -4802px 0px en este caso.
Te voy a dar una explicación resumida del código.
Pero antes debes conocer una función, la función requestAnimationFrame().
Si ya la conoces perfecto, pero sino, aquí tienes más información:
developer mozilla: requestAnimationFrame
CSS Tricks: requestAnimationFrame
Pero te lo voy a explicar a modo de resumen para que puedas seguir con este ejercicio:
requestAnimationFrame(), informa al navegador que quieres realizar una animación y solicita que el navegador programe el repintado de la ventana para el próximo ciclo de animación.
El método acepta como argumento una función a la que llamar antes de efectuar el repintado.
El funcionamiento sería el siguiente:
1 – El usuario hace scroll y lanzamos una función de callback, llamada scrollCallback.
// EVENTS
window.addEventListener("wheel", scrollCallback);
window.addEventListener("scroll", scrollCallback);
2 – En esta función de callback, comprobamos el valor de la variable ticking.
// SCROLL CALLBACK
function scrollCallback(e) {
if(!ticking) {
requestAnimationFrame(update);
}
ticking = true;
}
Si el valor de ticking es true, significa que ya se ha añadido para ejecutar en el siguiente frame de animación del navegador la función update() mediante el uso de requestAnimationFrame().
Si el valor es false, significa que no se ha añadido aún la función update() y en ese caso le informamos al navegador que lance la función update() en el siguiente frame de animación.
3 – El navegador lanza la función update().
function update() {
// RESET TICK TO FALSE
ticking = false;
// GET CURRENT SCROLL POSITION
var st = window.pageYOffset || document.documentElement.scrollTop;
if (st > lastScrollTop){
// DOWN SCROLL
playTimeline();
} else if(st < lastScrollTop) {
// UP SCROLL
reverseTimeline();
}
// SAVE CURRENT SCROLLTOP
lastScrollTop = st;
// CLEAR TIMEOUT SCROLL
window.clearTimeout(scrollEndCallback);
// SET NEW TIMEOUT TO TRIGGER SCROLLEND CALLBACK
scrollEndCallback = setTimeout(function() {
// SCROLLEND
pauseTimeline();
}, 66);
}
Nuestra función update(), lo primero que hace es guardar en la variable ticking el valor false, para que en caso de seguir haciendo scroll, volver a llamar la función update() para actualizar de nuevo la animación.
Después de eso, obtenemos la distancia de scroll total del documento y lo guardamos en la variable st.
Ahora, comparamos la variable st con la variable lastScrollTop que contiene la antigua posición de scroll del documento.
Al compararlas, sabremos la dirección del scroll del usuario.
Ya que si la antigua posición del scroll era 10px y la de ahora es 200px, significa que el usuario está haciendo scroll hacia el final del documento.
Esto lo usaremos para determinar si nuestra animación se reproduce normal o al revés.
Si está haciendo scroll hacia arriba, lanzamos la función reverseTimeline().
// REVERSE
function reverseTimeline(){
// LOOP FOR REVERSE MODE
if(global_tl.progress()==0){
global_tl.progress(1);
}
global_tl.reverse();
}
Si se está haciendo scroll hacia abajo, lanzamos la función playTimeline().
// PLAY
function playTimeline(){
global_tl.play();
}
Ahora hay que volver a guardar la posición actual de scroll en nuestra variable lastScrollTop, para que la próxima vez que se lance el evento de scroll se pueda volver a comprobar la dirección de navegación del usuario.
Después, evitamos que se ejecute una función establecida previamente con setTimeout(), si es que estaba preparada para ser llamada.
Ahora dejaremos preparada una función de callback para ejecutar.
Esta función la dejaremos preparada con setTimeout() y 66 milisegundos de espera, y esto nos dirá cuando ha acabado el usuario de hacer scroll.
¿Porqué 66 milisegundos de espera antes de lanzar la función?
Pues en una palabra te diría compatibilidad.
Pero aquí tienes una explicación más extensa:
¿Porqué 66 milisegundos?
Una vez sabemos que el usuario ha terminado realmente de hacer scroll, pararemos nuestra scroll animation con la función pauseTimeline()
// PAUSE
function pauseTimeline(){
global_tl.pause();
}
Ya está!
Lo importante aquí es saber cuando se empieza a hacer scroll, la dirección del scroll y cuando se termina de hacer scroll.
Cuando se empieza a hacer scroll es fácil de saber, añadiendo los listeners de scroll al documento.
Saber la dirección de scroll del usuario también es relativamente fácil de saber, tan solo necesitamos comprobar la antigua posición de scroll conocida del documento y la de después de haber hecho scroll el usuario.
Y para saber cuando acaba el usuario de hacer scroll, preparamos una función a ejecutar con setTimeout().
Tendrá 66 milisegundos de espera antes de lanzarse dicha función, por lo que, mientras el usuario está haciendo scroll, la función update() se seguirá ejecutando mucho antes de 66 milisegundos y seguirá limpiando el setTimeout() antiguo.
Mientras se está haciendo scroll nunca se llegará a lanzar la función del setTimeout().
Una vez hemos parado de hacer scroll, ya no se lanzará más la función update(), y eso quiere decir que tampoco se limpiará el setTimeout().
Entonces, pasados 66 milisegundos desde el último momento que se hizo scroll, se ejecutará la función de callback del setTimeout(), qué querrá decir que el usuario ya ha terminado de hacer scroll realmente.
Esto nos dirá que tenemos que pausar la animación.
Si el usuario vuelve a hacer scroll, pues volverá a ejecutarse la función de callback scrollCallback(), se volverá a comprobar la dirección de la animación del usuario y en función de la dirección se activará nuestra animación de manera normal o al revés.
Una vez pasados 66 milisegundos desde el último scroll capturado, querrá decir que el usuario ya terminó de nuevo de hacer scroll, y se volverá a pausar la scroll animation actual.
Y así siempre.
¡Bueno pues ya está!
Si tienes alguna duda o sugerencia ponla en los comentarios del vídeo de Youtube o envía un correo a info@animaticss.com.
Si usas esta animación en alguna web, ¡añádela en los comentarios o envíamela para que pueda verla!
Haré un vídeo enseñando las webs que me enviéis donde uséis esta scroll animation, para dar ejemplos de uso.
Si te ha gustado la nomenclatura que he usado en el código, se llama BEMIT, y he creado un curso en Udemy donde la explico al detalle.
Aquí te dejo otro artículo donde te explico mejor que puedes encontrar en ese curso, por si quieres mejorar tu metodología front end y tener bien organizado tu código.
¿Qué es BEMIT CSS?
Si te ha gustado el vídeo no olvides reventar el botón de like y suscribirte para no perderte más contenido como este.
Pero sobretodo, comparte este vídeo con tus amigos front end, ¡quizás quieren hacer esta scroll animation en alguna web!
También me puedes seguir por instagram: animaticss donde también estaré subiendo contenido.
Nos vemos muy pronto, un fuerte abrazo y a maquetar!