1. Web Design
  2. HTML/CSS
  3. Animation

Cómo animar durante el desplazamiento con JavaScript puro

Scroll to top

() translation by (you can also view the original English article)

Hacer que los elementos aparezcan según su posición de desplazamiento es una opción de diseño muy popular al crear páginas web, pero normalmente implica el uso de un plugin o una biblioteca.

En este tutorial, aprenderás cómo implementar animaciones durante el desplazamiento utilizando CSS y JavaScript puro. La principal ventaja al usar una implementación personalizada (a diferencia de una biblioteca) es que nos permite optimizar nuestras funciones para la accesibilidad y el rendimiento.

Demostración de la animación durante el desplazamiento

Aquí se muestra un ejemplo de cómo funciona la animación de los elementos al desplazarnos:

Nuestra implementación depende de CSS para las animaciones y de JavaScript para controlar la activación de los estilos necesarios. Empezaremos creando el diseño.

1. Definir la estructura de la página

Vamos a crear el diseño de nuestra página con HTML y luego asignaremos un nombre de clase común a los elementos que queremos animar en el desplazamiento. Este nombre de clase es al que nos dirigiremos en JavaScript.

En la demostración anterior, a los elementos se les asignó el nombre de clase js-scroll, por lo que el HTML se parecerá a esto:

1
<header>
2
  <!--this is where the content of the header goes-->
3
</header>
4
<section class="scroll-container">
5
    <div class="scroll-element js-scroll">
6
    </div>
7
    <div class="scroll-caption">
8
      This animation fades in from the top.
9
    </div>
10
</section>

2. Estilizar con CSS

CSS realiza gran parte del trabajo pesado, pues determina el estilo de la animación de cada elemento. En este caso, animaremos los elementos con el nombre de clase scrolled.

Este es un ejemplo de una animación simple con efecto fade-in (aparición gradual):

1
.js-scroll {
2
  opacity: 0;
3
  transition: opacity 500ms;
4
}
5
6
.js-scroll.scrolled {
7
  opacity: 1;
8
}

Con este código, cualquier elemento js-scroll en la página se oculta con una opacidad de 0 hasta que se le aplica el nombre de la clase scrolled.

3. Elegir los elementos objetivo con JavaScript

En cuanto tengamos nuestro diseño y estilos, crearemos las funciones de JavaScript para asignar el nombre de la clase a los elementos cuando se desplacen a la vista. También vamos a desvanecer los elementos en JavaScript en lugar de CSS, ya que queremos que los elementos sean visibles en caso de que un navegador no tenga JavaScript habilitado.

Desglosaremos la lógica así:

  1. Obtener todos los elementos js-scroll de la página
  2. Desvanecer los elementos
  3. Detectar cuando el elemento está dentro de la ventana gráfica
  4. Asignar el nombre de la clase scrolled al elemento si está a la vista.

Elegir los elementos objetivo de la página

Elegiremos todos los elementos js-scroll de la página usando el método document.querySelectorAll(). De esta forma:

1
const scrollElements = document.querySelectorAll(".js-scroll");

Desvanecimiento de los elementos

Primero, necesitamos eliminar opacity:0 para .js-scroll en nuestro CSS. Luego incluimos esta línea en nuestro JavaScript:

1
scrollElements.forEach((el) => {
2
  el.style.opacity = 0
3
})

Esto permite que los elementos tengan su estilo predeterminado si JavaScript está deshabilitado en el navegador.

Detectando cuando un elemento está a la vista

Podemos detectar cuándo un elemento está a la vista del usuario determinando si la distancia del elemento desde la parte superior de la página es menor que la altura de la parte visible de la página.

En JavaScript, utilizamos el método getBoundingClientRect().top para obtener la distancia de un elemento desde la parte superior de la página, y window.innerHeight o document.documentElement.clientHeight para obtener la altura de la ventana gráfica.

Fuente: Element.getBoundingClientRect () - API web | MDN

Crearemos una función elementInView usando la lógica anterior:

1
const elementInView = (el) => {
2
  const elementTop = el.getBoundingClientRect().top;
3
4
  return (
5
    elementTop <= (window.innerHeight || document.documentElement.clientHeight)
6
  );
7
};

Podemos modificar esta función para detectar cuándo el elemento se ha desplazado x píxeles en la página, o detectar cuándo se ha desplazado un porcentaje de la página.

1
const elementInView = (el, scrollOffset = 0) => {
2
  const elementTop = el.getBoundingClientRect().top;
3
4
  return (
5
    elementTop <= 
6
    ((window.innerHeight || document.documentElement.clientHeight) - scrollOffset)
7
  );
8
};

En este caso, la función devuelve true si el elemento se ha desplazado por la cantidad de scrollOffset en la página. Al modificar la lógica nos da una función diferente para dirigirnos a los elementos en función del porcentaje de desplazamiento.

1
const elementInView = (el, percentageScroll = 100) => {
2
  const elementTop = el.getBoundingClientRect().top;
3
4
  return (
5
    elementTop <= 
6
    ((window.innerHeight || document.documentElement.clientHeight) * (percentageScroll/100))
7
  );
8
};

Un beneficio adicional de una implementación personalizada es que podemos definir la lógica para adaptarse a nuestras necesidades específicas.

Nota: Es posible utilizar la API Intersection Observer para lograr el mismo efecto; sin embargo, al momento de escribir este artículo, Intersection Observer no es compatible con Internet Explorer, por lo que no se adapta a nuestro objetivo de que «funcione en todos los navegadores».

Asignar el nombre de clase al elemento

Ahora que podemos detectar si nuestro elemento se ha desplazado a la página, necesitaremos definir una función para controlar la visualización del elemento; en este caso, mostramos el elemento asignando el nombre de clase scrolled.

1
const displayScrollElement = (element) => {
2
  element.classList.add("scrolled");
3
};

Luego combinaremos nuestra lógica con la función de visualización y usaremos el método forEach para llamar a la función en todos los elementos js-scroll.

1
const handleScrollAnimation = () => {
2
  scrollElements.forEach((el) => {
3
    if (elementInView(el, 100)) {
4
      displayScrollElement(el);
5
    }
6
  })
7
}

Una función opcional es restablecer el elemento a su estado predeterminado cuando ya no esté a la vista. Podemos hacer eso definiendo una función hideScrollElement e incluyéndola en una declaración else a nuestra función anterior:

1
const hideScrollElement = (element) => {
2
  element.classList.remove("scrolled");
3
};
4
5
const handleScrollAnimation = () => {
6
  scrollElements.forEach((el) => {
7
    if (elementInView(el, 100)) {
8
      displayScrollElement(el);
9
    } else {
10
      hideScrollElement(el);
11
    }
12
  })
13
}

Por último, pasaremos el método anterior a un detector de eventos de desplazamiento en la ventana para que se ejecute cada vez que el usuario se desplace.

1
window.addEventListener('scroll', () => {
2
  handleScrollAnimation();
3
})

¡Y listo!, hemos implementado todas las funciones que necesitamos para animar durante el desplazamiento.

Podemos ver cómo funciona la lógica en esta demostración:

El código completo de JavaScript se ve así:

1
const scrollOffset = 100;
2
3
const scrollElement = document.querySelector(".js-scroll");
4
5
const elementInView = (el, offset = 0) => {
6
  const elementTop = el.getBoundingClientRect().top;
7
8
  return (
9
    elementTop <= 
10
    ((window.innerHeight || document.documentElement.clientHeight) - offset)
11
  );
12
};
13
14
const displayScrollElement = () => {
15
  scrollElement.classList.add('scrolled');
16
}
17
18
const hideScrollElement = () => {
19
  scrollElement.classList.remove('scrolled');
20
}
21
22
const handleScrollAnimation = () => {
23
  if (elementInView(scrollElement, scrollOffset)) {
24
      displayScrollElement();
25
  } else {
26
    hideScrollElement();
27
  }
28
}
29
30
window.addEventListener('scroll', () => {
31
  handleScrollAnimation();
32
})

El código CSS, así:

1
.js-scroll {
2
  width: 50%;
3
  height: 300px;
4
  background-color: #DADADA;
5
  transition: background-color 500ms;
6
}
7
8
.js-scroll.scrolled {
9
  background-color: aquamarine;
10
}

4. Más animaciones con CSS

Veamos la primera demostración nuevamente:

Vemos que los elementos aparecen con distintas animaciones. Esto se hizo asignando diferentes animaciones de CSS a los nombres de las clases. El HTML de esta demostración se ve así:

1
<section class="scroll-container">
2
  <div class="scroll-element js-scroll fade-in">
3
  </div>
4
  <div class="scroll-caption">
5
    This animation fades in.
6
  </div>
7
</section>
8
<section class="scroll-container">
9
  <div class="scroll-element js-scroll fade-in-bottom">
10
  </div>
11
  <div class="scroll-caption">
12
    This animation slides in to the top.
13
  </div>
14
</section>
15
<section class="scroll-container">
16
  <div class="scroll-element js-scroll slide-left">
17
  </div>
18
  <div class="scroll-caption">
19
    This animation slides in from the left.
20
  </div>
21
</section>
22
<section class="scroll-container">
23
  <div class="scroll-element js-scroll slide-right">
24
  </div>
25
  <div class="scroll-caption">
26
    This animation slides in from the right.
27
  </div>
28
</section>

Nota: Las clases junto a la clase js-scroll son a las que nos dirigimos en CSS para controlar las diferentes animaciones. En nuestra hoja de estilos CSS, tendremos:

1
.scrolled.fade-in {
2
  animation: fade-in 1s ease-in-out both;
3
}
4
5
.scrolled.fade-in-bottom {
6
  animation: fade-in-bottom 1s ease-in-out both;
7
}
8
9
.scrolled.slide-left {
10
  animation: slide-in-left 1s ease-in-out both;
11
}
12
13
.scrolled.slide-right {
14
  animation: slide-in-right 1s ease-in-out both;
15
}
16
17
@keyframes slide-in-left {
18
  0% {
19
    transform: translateX(-100px);
20
    opacity: 0;
21
  }
22
  100% {
23
    transform: translateX(0);
24
    opacity: 1;
25
  }
26
}
27
28
@keyframes slide-in-right {
29
  0% {
30
    transform: translateX(100px);
31
    opacity: 0;
32
  }
33
  100% {
34
    transform: translateX(0);
35
    opacity: 1;
36
  }
37
}
38
39
@keyframes fade-in-bottom {
40
  0% {
41
    transform: translateY(50px);
42
    opacity: 0;
43
  }
44
  100% {
45
    transform: translateY(0);
46
    opacity: 1;
47
  }
48
}
49
50
@keyframes fade-in {
51
  0% {
52
    opacity: 0;
53
  }
54
  100% {
55
    opacity: 1;
56
  }
57
}

No necesitamos hacer ningún cambio en el código JavaScript, pues la lógica sigue siendo la misma. Esto significa que podemos tener cualquier cantidad de animaciones diferentes en una página sin escribir nuevas funciones.

5. Aumentando del rendimiento con throttle (acelerador)

Siempre que incluimos una función en un scroll listener, esa función es llamada cada vez que el usuario se desplaza por la página. El desplazamiento en una página de 500px puede ocasionar que se llame a una función al menos 50 veces. Si intentamos incluir muchos elementos en la página, puede provocar que nuestra página se ralentice significativamente.

La función throttle al rescate

Podemos reducir el número de veces que se llama a una función con una «función throttle». Es una función de orden superior que llama a la función que se le ha pasado solamente una vez durante un intervalo de tiempo especificado.

Es sobre todo útil con eventos de desplazamiento, pues no necesitamos detectar cada píxel desplazado por el usuario. Por ejemplo, si tenemos una función throttle con un temporizador de 100 ms, la función se llamará solo una vez por cada 100 ms que el usuario se desplace.

Una función throttle se puede implementar en JavaScript de esta forma:

1
//initialize throttleTimer as false

2
let throttleTimer = false;
3
4
const throttle = (callback, time) => {
5
    //don't run the function while throttle timer is true

6
    if (throttleTimer) return;
7
    
8
    //first set throttle timer to true so the function doesn't run

9
    throttleTimer = true;
10
    
11
    setTimeout(() => {
12
        //call the callback function in the setTimeout and set the throttle timer to false after the indicated time has passed 

13
        callback();
14
        throttleTimer = false;
15
	}, time);
16
}

Podemos modificar nuestro objeto window en el event listener de desplazamiento para que se vea así

1
window.addEventListener('scroll', () => {
2
  throttle(handleScrollAnimation, 250);
3
})

Ahora nuestra función handleScrollAnimation se llama cada 250ms mientras el usuario se esté desplazando.

Así es como se ve la demostración actualizada:

6. Mejorando la accesibilidad

El rendimiento no es el único requisito al implementar una función personalizada; también debemos diseñar para la accesibilidad. Diseñar para la accesibilidad significa tener en cuenta las elecciones y circunstancias de los usuarios. Tal vez algunos usuarios no quieran tener animaciones en absoluto, por lo que debemos tenerlo en cuenta. 

La media query de movimiento reducido

Podemos hacer eso con la query prefers-reduced-motion y una implementación de JavaScript.

«prefers-reduced-motion [...] se utiliza para detectar si el usuario ha solicitado que el sistema minimice la cantidad de movimiento no esencial» – MDN

Modificando nuestro código anterior, la query se vería así en CSS:

1
@media (prefers-reduced-motion) {
2
  .js-scroll {
3
    opacity: 1;
4
  }
5
  .scrolled {
6
    animation: none !important;
7
  }
8
}

Con estas líneas de código, nos aseguramos de que los elementos animados estén siempre visibles y que la animación esté desactivada para todos los elementos.

La query «prefers-reduced-motion» no es completamente compatible con todos los navegadores, por lo que debemos incluir una solución alternativa con JavaScript:

1
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
2
3
window.addEventListener("scroll", () => {
4
  //check if mediaQuery exists and if the value for mediaQuery does not match 'reduce', return the scrollAnimation.

5
  if (mediaQuery && !mediaQuery.matches) {
6
    handleScrollAnimation()
7
  }
8
});

De esta forma, si el usuario prefiere un movimiento reducido, la función handleScrollAnimation no es llamada en absoluto.

Así es cómo se anima durante el desplazamiento con JavaScript

¡Ahora tenemos una implementación con alto rendimiento y completamente accesible con la función «animar durante el desplazamiento» que funciona en todos los navegadores!

Más tutoriales prácticos de JavaScript

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Web Design tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.