Manejo de Eventos de Scroll Sin Afectar el Rendimiento

Los eventos de scroll se disparan decenas de veces por segundo. Sin un manejo adecuado, arruinarán el rendimiento de tu sitio, agotarán las baterías de dispositivos móviles y crearán experiencias de usuario entrecortadas. Aquí te mostramos cómo optimizar los manejadores de scroll usando throttling, debouncing y listeners pasivos.
Puntos Clave
- El throttling limita la ejecución de funciones a intervalos fijos para actualizaciones consistentes
- El debouncing espera hasta que el scroll se detenga antes de ejecutar operaciones costosas
- Los listeners pasivos permiten optimizaciones inmediatas del navegador
- Intersection Observer elimina los eventos de scroll para detección de visibilidad
El Problema: Por Qué los Eventos de Scroll Sin Procesar Destruyen el Rendimiento
Cada movimiento de scroll dispara múltiples eventos—frecuentemente más de 60 por segundo. Cuando adjuntas cálculos pesados a estos eventos, le estás pidiendo al navegador que:
- Bloquee el hilo principal repetidamente
- Impida el scroll suave
- Aumente dramáticamente el uso de CPU
- Agote la batería en dispositivos móviles
// NO HAGAS ESTO - se dispara constantemente
window.addEventListener('scroll', () => {
calculateExpensiveAnimation();
updateNavigationState();
checkElementVisibility();
});
Solución 1: Throttle para Actualizaciones Consistentes
El throttling limita la ejecución de funciones a un intervalo fijo. Perfecto para animaciones basadas en scroll o indicadores de progreso que necesitan actualizaciones regulares.
function throttle(func, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
return func.apply(this, args);
}
};
}
// Se dispara como máximo cada 100ms
window.addEventListener('scroll', throttle(() => {
updateScrollProgress();
}, 100));
Cuándo usar throttling:
- Barras de progreso de scroll
- Efectos parallax
- Actualizaciones de estado de navegación
- Seguimiento de posición en tiempo real
Solución 2: Debounce para Valores Finales
El debouncing espera hasta que el scroll se detenga antes de ejecutar. Ideal para operaciones costosas que solo necesitan la posición final del scroll.
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// Se dispara 200ms después de que el scroll se detenga
window.addEventListener('scroll', debounce(() => {
saveScrollPosition();
loadMoreContent();
}, 200));
Cuándo usar debouncing:
- Disparadores de scroll infinito
- Seguimiento de analytics
- Guardar posición de scroll
- Cálculos pesados del DOM
Discover how at OpenReplay.com.
Solución 3: Listeners Pasivos para Rendimiento Instantáneo
Los listeners pasivos le dicen al navegador que no llamarás preventDefault()
, habilitando optimizaciones inmediatas de scroll.
// El navegador puede optimizar el scroll inmediatamente
window.addEventListener('scroll', handleScroll, { passive: true });
Esta simple bandera mejora el rendimiento del scroll al permitir que el navegador omita verificar si vas a prevenir el comportamiento predeterminado. Los navegadores móviles se benefician especialmente de esta optimización.
Combinando Técnicas para Máximo Rendimiento
Para interacciones complejas de scroll, combina múltiples enfoques:
// Manejador con throttle y listener pasivo
const optimizedScroll = throttle(() => {
requestAnimationFrame(() => {
updateUI();
});
}, 16); // ~60fps
window.addEventListener('scroll', optimizedScroll, { passive: true });
Alternativa Moderna: Intersection Observer
Para detección de visibilidad, omite completamente los eventos de scroll:
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
lazyLoadImage(entry.target);
}
});
});
observer.observe(document.querySelector('.lazy-image'));
Intersection Observer maneja la detección de visibilidad sin ningún listener de scroll, ofreciendo rendimiento superior para lazy loading y animaciones disparadas por scroll.
Guía de Decisión Rápida
Técnica | Caso de Uso | Frecuencia de Actualización |
---|---|---|
Throttle | Barras de progreso, parallax | Intervalos fijos |
Debounce | Guardar estado, cargar contenido | Después de que el scroll se detenga |
Pasivo | Cualquier manejador de scroll | Siempre (cuando sea posible) |
Intersection Observer | Detección de visibilidad | Sin eventos de scroll |
Consejos de Implementación
- Siempre usa listeners pasivos a menos que necesites
preventDefault()
- Comienza con delays de throttle de 16ms para animaciones a 60fps
- Usa delays de debounce de 200-300ms para acciones disparadas por el usuario
- Considera Lodash para implementaciones probadas en batalla
- Perfila con Chrome DevTools para medir las ganancias reales de rendimiento
Conclusión
Los manejadores de scroll no optimizados son asesinos del rendimiento. El throttling te da actualizaciones controladas para animaciones, el debouncing maneja valores finales eficientemente, y los listeners pasivos proporcionan optimizaciones instantáneas del navegador. Para detección de visibilidad, omite completamente los eventos de scroll con Intersection Observer. Elige la técnica correcta para tu caso de uso, y tus usuarios te lo agradecerán con la vida de su batería.
Preguntas Frecuentes
El throttling ejecuta tu función a intervalos regulares durante el scroll, como cada 100ms. El debouncing espera hasta que el scroll se detenga completamente, luego ejecuta una vez. Usa throttling para actualizaciones continuas y debouncing para valores finales.
No, los listeners pasivos explícitamente le dicen al navegador que no llamarás preventDefault. Si necesitas prevenir el comportamiento predeterminado del scroll, establece passive en false, pero esto impacta el rendimiento. Considera enfoques alternativos primero.
Usa Intersection Observer para cualquier lógica basada en visibilidad como lazy loading, disparadores de scroll infinito, o animaciones en scroll. Es más eficiente que los eventos de scroll y maneja automáticamente los cálculos de viewport sin verificación manual de posición.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay. — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.