Desplazamiento en JavaScript
Los eventos de desplazamiento en JavaScript permiten reaccionar a la posición del usuario en la página, siendo la base de efectos parallax, carga diferida y más.
Entendiendo los eventos y técnicas de desplazamiento en JavaScript
Los eventos de desplazamiento en JavaScript te permiten reaccionar a la posición del usuario en una página. Esta es la base de funcionalidades como efectos parallax, activación de animaciones según la posición de desplazamiento, implementación de scroll infinito, botones "volver arriba", encabezados fijos y barras de progreso de lectura. Esta guía explica cómo leer la posición de desplazamiento actual, cómo desplazarse mediante programación, cómo gestionar el evento scroll de forma eficiente y cuándo usar el moderno IntersectionObserver en su lugar.
Leer la posición de desplazamiento actual
Antes de reaccionar al desplazamiento, normalmente necesitas saber hasta dónde se ha desplazado la página. Las propiedades más fiables están en el objeto window:
window.scrollY— distancia de desplazamiento vertical en píxeles (tambiénwindow.pageYOffset, un alias más antiguo).window.scrollX— distancia de desplazamiento horizontal en píxeles (aliaswindow.pageXOffset).
Para un elemento con scroll individual, usa element.scrollTop y element.scrollLeft en su lugar. Estas propiedades son de lectura/escritura: asignarles un valor desplaza el elemento hasta esa posición.
// How far down the whole page has the user scrolled?
console.log(window.scrollY); // e.g. 0 at the top, 420 partway down
// Total scrollable height of the document
const docHeight = document.documentElement.scrollHeight;
const winHeight = window.innerHeight;
// How close to the bottom are we (0 = top, 1 = bottom)?
const progress = window.scrollY / (docHeight - winHeight);
console.log(progress);Para desplazamientos por elemento y un análisis más detallado de getBoundingClientRect, consulta Coordenadas en JavaScript y Tamaños de ventana y desplazamiento.
Desplazamiento mediante programación
No solo puedes escuchar el desplazamiento — también puedes activarlo. Estos métodos aceptan la opción behavior: 'smooth' para una transición animada en lugar de un salto instantáneo:
window.scrollTo(x, y)— desplaza a una posición absoluta.window.scrollBy(dx, dy)— desplaza una cantidad relativa desde la posición actual.element.scrollIntoView(options)— desplaza para que un elemento específico sea visible.
// Jump to the very top, smoothly
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
// Nudge down by one viewport height
window.scrollBy({ top: window.innerHeight, behavior: 'smooth' });
// Bring an element into view (e.g. after navigating to an anchor)
document.querySelector('#section-2')
.scrollIntoView({ behavior: 'smooth', block: 'start' });Esta es la forma correcta de implementar un botón "volver arriba" o una navegación de anclaje interna con desplazamiento suave. Usa querySelector para obtener primero el elemento destino.
El evento scroll en JavaScript
El evento scroll se dispara cuando se desplaza la vista del documento o un elemento con scroll. Es uno de los eventos más utilizados para diseños dinámicos e interactivos.
Conceptos clave
- Frecuencia del evento: El evento
scrollpuede dispararse decenas de veces por segundo, por lo que su manejador se ejecuta con mucha frecuencia. Realizar trabajo intensivo (lecturas de maquetado, escrituras en el DOM, llamadas a la red) en cada disparo provoca un desplazamiento entrecortado. La solución estándar es throttle o debounce en el manejador. - Desplazamiento en
windowvs. en elementos: Puedes escuchar el scroll en toda la ventana (window.addEventListener('scroll', ...)) o en un elemento específico con overflow-scroll (el.addEventListener('scroll', ...)). scrollno se propaga por burbujas: Un eventoscrollen un elemento no sube hasta el documento, por lo que debes adjuntar el listener al elemento que realmente se desplaza.
Throttle vs. Debounce
Ambos limitan la frecuencia de ejecución del manejador, pero se comportan de forma diferente:
- Debounce espera hasta que el desplazamiento se detiene durante
waitms y luego se ejecuta una vez. Útil para "hacer algo cuando el usuario termina de desplazarse" (p. ej., guardar la posición de scroll). - Throttle se ejecuta como máximo una vez cada
waitms durante el desplazamiento. Mejor para efectos continuos como barras de progreso, donde quieres actualizaciones regulares mientras se desplaza.
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function throttle(func, wait) {
let last = 0;
return function (...args) {
const now = Date.now();
if (now - last >= wait) {
last = now;
func.apply(this, args);
}
};
}
// Debounce: runs once 100ms after scrolling stops
window.addEventListener('scroll', debounce(() => {
// handle scroll logic here
}, 100));
// Throttle: runs at most every 100ms while scrolling
window.addEventListener('scroll', throttle(() => {
// update a progress bar, etc.
}, 100));Ambos auxiliares basados en setTimeout se apoyan en las API de planificación de setTimeout / setInterval.
Ejemplos prácticos de manejo de eventos de scroll
Ejemplo 1: Mostrar/ocultar la navegación al desplazarse
Este ejemplo muestra cómo ocultar una barra de navegación al desplazarse hacia abajo y mostrarla al desplazarse hacia arriba, un patrón muy común en muchos sitios web modernos para maximizar el espacio en pantalla.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Scroll Event Navigation Example</title>
<style>
#navbar {
position: fixed;
top: 0;
width: 100%;
background-color: #333;
color: white;
text-align: center;
padding: 10px;
transition: top 0.3s;
}
body {
padding: 0;
margin: 0;
height: 1500px; /* to ensure scrolling */
font-family: Arial, sans-serif;
}
</style>
</head>
<body>
<p style="display: flex; justify-content: center; align-items: center; margin-top: 50vh;"><strong>When you scroll down, the navigation bar disappears. Scroll back up, and it reappears!</strong></p>
<div id="navbar">Navigation Bar</div>
<script>
let lastScrollTop = 0;
window.addEventListener(
"scroll",
function () {
let currentScroll = window.pageYOffset || document.documentElement.scrollTop;
if (currentScroll > lastScrollTop) {
document.getElementById("navbar").style.top = "-50px"; // Adjust based on nav height
} else {
document.getElementById("navbar").style.top = "0px";
}
lastScrollTop = currentScroll <= 0 ? 0 : currentScroll; // For Mobile or negative scrolling
},
false
);
</script>
</body>
</html>Explicación:
- El script registra la última posición de scroll y la compara con la posición actual. Si la posición actual es mayor, significa que el usuario está bajando, por lo que la barra de navegación se oculta ajustando su posición superior fuera de pantalla.
- Al desplazarse hacia arriba, la barra de navegación vuelve a aparecer.
Ejemplo 2: Activación de animación al hacer scroll
Este ejemplo muestra cómo activar animaciones cuando los elementos entran en el viewport durante el desplazamiento.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Scroll Animation Trigger</title>
<style>
.box {
width: 100px;
height: 100px;
background: red;
opacity: 0;
transition: opacity 2s;
margin: 600px auto; /* Ensures it starts out of view */
}
</style>
</head>
<body>
<p>Keep scrolling down to see the animation!</p>
<div class="box"></div>
<script>
let hasAnimated = false;
window.addEventListener('scroll', function() {
const box = document.querySelector('.box');
const rect = box.getBoundingClientRect();
if (rect.top < window.innerHeight && !hasAnimated) {
box.style.opacity = 1; // Fade in the box when it comes into view
hasAnimated = true;
}
});
</script>
</body>
</html>Explicación:
- Verificación de visibilidad: El script comprueba si la parte superior del elemento
.boxestá dentro del viewport y cambia su opacidad a 1, activando un efecto de aparición gradual. Una bandera evita que la animación se vuelva a activar en eventos de scroll posteriores.
Ejemplo 3: Efecto de desplazamiento parallax
Este ejemplo demuestra un efecto parallax simple donde la imagen de fondo se mueve a una velocidad diferente que el contenido del primer plano al desplazarse hacia abajo.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Enhanced Parallax Scrolling</title>
<style>
body, html {
height: 100%;
margin: 0;
font-family: Arial, sans-serif;
overflow-x: hidden; /* Prevent horizontal scroll */
}
.parallax {
height: 100vh; /* Full height of the viewport */
position: relative;
background: url('https://via.placeholder.com/1920x1080') no-repeat center center;
background-size: cover;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 36px;
letter-spacing: 1px;
}
.content {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: white;
color: #333;
font-size: 24px;
padding: 0 20px;
text-align: center;
box-sizing: border-box;
border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
</style>
</head>
<body>
<div class="content">Scroll down to see the parallax effect!</div>
<div class="parallax">Stunning Parallax!</div>
<div class="content">Keep scrolling to see more effects.</div>
<div class="parallax"></div>
<div class="content">You have reached the end. Amazing, right?</div>
<script>
document.addEventListener('scroll', function() {
document.querySelectorAll('.parallax').forEach(function(el) {
const factor = 0.5; // Change this for more or less parallax
const offset = window.pageYOffset * factor - 300; // Adjusts the starting position of background
el.style.backgroundPositionY = offset + 'px';
});
});
</script>
</body>
</html>Explicación:
- Estilos CSS: La clase
.parallaxconfigura la imagen de fondo para que cubra el contenedor y la centra. El ejemplo depende completamente de JavaScript para el posicionamiento, evitandobackground-attachment: fixedde CSS, que puede causar problemas de rendimiento en dispositivos móviles. - Funcionalidad JavaScript: Al desplazarse, el script calcula una nueva posición vertical para la imagen de fondo a partir del desplazamiento de scroll. Al ajustar
backgroundPositionYde forma dinámica, la imagen se mueve a una velocidad diferente que el contenido de la página, creando el efecto de profundidad parallax.
En resumen, al desplazarte, las imágenes de fondo se mueven más lento que el texto, haciendo que parezcan estar a una profundidad diferente.
La alternativa moderna: IntersectionObserver
Para el caso habitual de "hacer algo cuando un elemento se vuelve visible" (carga diferida, animaciones de aparición, scroll infinito), IntersectionObserver es el enfoque moderno recomendado. En lugar de ejecutar tu código en cada evento scroll y leer posiciones manualmente, el navegador te notifica de forma asíncrona cuando un elemento cruza un umbral — mucho más eficiente y sin interrupciones.
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible'); // run once it enters view
observer.unobserve(entry.target); // stop watching it
}
});
},
{ threshold: 0.25 } // fire when 25% of the element is visible
);
document.querySelectorAll('.box').forEach((el) => observer.observe(el));Usa el evento scroll cuando necesites una lectura continua de la posición de desplazamiento (parallax, barras de progreso). Usa IntersectionObserver cuando solo te importe que un elemento entró o salió del viewport. Es conceptualmente similar a la API MutationObserver.
Prevenir y restaurar el desplazamiento
Un requisito habitual en interfaces de usuario es bloquear el scroll de la página mientras un modal o menú está abierto. No puedes cancelar de forma fiable el evento scroll con preventDefault() (se dispara después de que el desplazamiento ya ocurrió). En su lugar, alterna el CSS overflow:
// Lock scrolling (e.g. when opening a modal)
document.body.style.overflow = 'hidden';
// Restore it when the modal closes
document.body.style.overflow = '';Conclusión
Manejar los eventos de scroll de forma efectiva es una habilidad crucial para los desarrolladores web, ya que permite crear sitios más interactivos y optimizados en cuanto a rendimiento. Ya sea que estés implementando mejoras en la interfaz de usuario como barras de navegación dinámicas o usando un efecto parallax en tu página, entender y gestionar correctamente el desplazamiento en JavaScript puede mejorar drásticamente la experiencia del usuario.