El tercer parámetro de addEventListener: configuración avanzada y optimización de rendimiento

El objeto de configuración en addEventListener

Cuando utilizamos addEventListener, existe un tercer parámetro opcional que acepta un objeto con varias propiedades de configuración. Estas propiedades nos permiten controlar el comportamiento del listener de forma precisa.

Propiedades disponibles

  • capture: Valor booleano que determina si el listener se ejecuta en la fase de captura o en la fase de propagación (bubbling). Por defecto es false, lo que significa que se ejecuta en la fase de propagación.
  • once: Cuando se establece en true, el listener se ejecutará una sola vez y luego se eliminará automáticamente.
  • passive: Al establecerlo en true, se le indica al navegador que el listener nunca invocará preventDefault(). Esto tiene implicaciones importantes en el rendimiento.
  • signal: Permite asociar un objeto AbortSignal para poder cancelar el listener cuando se llame al método abort() del controlador correspondiente.

Ejemplo práctico


<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Demostración de opciones en addEventListener</title>
  <style>
    body {
      margin: 0;
      padding: 0;
      font-family: Arial, sans-serif;
    }
    .container {
      width: 200px;
      height: 200px;
      background-color: lightblue;
      margin: 20px;
    }
  </style>
</head>
<body>
  <div class="container"></div>
  <button id="cancelBtn">Cancelar</button>
  <script>
    const container = document.querySelector('.container');
    const cancelBtn = document.querySelector('#cancelBtn');

    // Configuración con capture
    container.addEventListener('click', (ev) => {
      console.log('Fase de captura: clic en container');
    }, { capture: true });

    // Configuración con once
    container.addEventListener('click', (ev) => {
      console.log('Una sola vez: clic en container');
    }, { once: true });

    // Configuración con passive
    container.addEventListener('touchstart', (ev) => {
      console.log('Passive: inicio de toque en container');
      // ev.preventDefault(); // No tendrá efecto
    }, { passive: true });

    // Configuración con signal
    const abortCtrl = new AbortController();
    const { signal } = abortCtrl;

    container.addEventListener('click', (ev) => {
      console.log('Signal: clic en container');
    }, { signal });

    cancelBtn.addEventListener('click', () => {
      abortCtrl.abort();
      console.log('Cancelado: listener removido');
    });
  </script>
</body>
</html>

Optimización de rendimiento con passive

La propiedad passive juega un papel crucial en la optimización del rendimiento de desplazamiento en páginas web.

El rol de preventDefault

En JavaScript, ciertos eventos desencadenan comportamientos predeterminados del navegador, como la navegación al hacer clic en un enlace o la entrada de texto al presionar teclas. El método preventDefault() permite cancelar estos comportamientos predeterminados.

Regiones de desplazamiento no rápido

El concepto de regiones de desplazamiento no rápido (non-fast scrollable regions) es fundamental para entender cómo el navegador gestiona la composición de la página. Cuando se registra un listener de eventos, el hilo de composición marca esa área como una región de desplazamiento no rápido.

Cuando un evento ocurre en estas regiones, el hilo de composición debe:

  1. Enviar el evento al hilo principal para su procesamiento
  2. Esperar a que el hilo principal complete la ejecución antes de continuar con la composición de nuevos fotogramas

Esto puede causar varios problemas:

  • Si la función de manejo de eventos tarda demasiado, los nuevos fotogramas se retrasan, provocando una sensación de tartamudeo
  • Si se llama a preventDefault(), el hilo de composición detiene la creación de nuevos fotogramas, eliminando el efecto de desplazamiento
  • Si el evento ocurre fuera de estas regiones, el hilo de composición puede generar nuevos fotogramas sin esperar al hilo principal

Al usar passive: true, se le indica al navegador que el listener no cancelará el comportamiento predeterminado, permitiendo que el hilo de composición continúe generando fotogramas sin esperar la respuesta del hilo principal.

Simulación de problemas de rendimiento


<html lang="es">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Demostración de tartamudeo</title>
  <style>
    .content {
      height: 3000px;
      background: linear-gradient(70deg, blue, pink);
    }
  </style>
</head>
<body>
  <div class="content"></div>
  <script>
    function blockThread(duration) {
      const start = Date.now();
      while (Date.now() < start + duration) {}
    }

    document.addEventListener(
      "wheel",
      (ev) => {
        if (Math.random() < 0.8) {
          console.log("Iniciando bloqueo ---------");
          blockThread(500);
          console.log("Fin del bloqueo ---------");
          console.log("\n");
        }
      },
      { passive: false }
    );
  </script>
</body>
</html>

En este código, se escucha el evento wheel y hay un 80% de probabilidad de ejecutar una tarea de 500ms, lo que provoca tartamudeo durante el desplazamiento. Al cambiar passive a true, el desplazamiento se vuelve fluido porque el hilo de composición no necesita esperar al hilo principal.

Diferencias entre eventos scroll y wheel

Frecuencia de activación

  • scroll: Se dispara con menor frecuencia, típicamente cuando el desplazamiento se detiene.
  • wheel: Se dispara con alta frecuencia, especialmente durante desplazamientos rápidos.

Mecanismo de procesamiento

  • scroll: Al dispararse después del desplazamiento, las operaciones costosas tienen menor impacto en la experiencia.
  • wheel: Debido a su alta frecuencia, las operaciones costosas bloquean el hilo principal y provocan tartamudeo.

Optimizaciones del navegador

Los navegadores modernos han optimizado el evento scroll para ofrecer una experiencia fluida incluso sin la opción passive. Sin embargo, el evento wheel no cuenta con estas optimizaciones, por lo que es más propenso a causar problemas de rendimiento.

Escenarios de uso recomendados

Eventos scroll

Botón de volver arriba: Mostrar un botón cuando el usuario se desplaza más allá de cierta posición.

const btnTop = document.getElementById('btnTop');
window.addEventListener('scroll', () => {
  btnTop.style.display = window.scrollY > 300 ? 'block' : 'none';
});
btnTop.addEventListener('click', () => {
  window.scrollTo({ top: 0, behavior: 'smooth' });
});

Carga infinita: Cargar contenido adicional cuando el usuario llega al final de la página.

window.addEventListener('scroll', () => {
  const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
  if (scrollTop + clientHeight >= scrollHeight) {
    fetchMoreContent();
  }
});

Eventos wheel

  • Zoom de elementos: Permitir acercar o alejar imágenes mediante la rueda del mouse
  • Detección de dirección: Determinar si el usuario se desplaza hacia arriba o hacia abajo

Etiquetas: addEventListener JavaScript performance passive wheel

Publicado el 8-23 03:15