Cómo corregir el bug de scroll causado por Iconify e IntersectionObserver v2.1.0

En interfaces administrativas con muchos iconos, formularios, tablas y modales, puede aparecer un comportamiento extraño: al hacer scroll en páginas extensas, la pantalla se congela por momentos o parece “rebotar”. Aunque visualmente se percibe como un salto, normalmente el problema viene de un ciclo de recalculado del navegador provocado por algún script que modifica el DOM mientras el usuario se desplaza.

En este caso, el origen del problema estaba relacionado con Iconify, específicamente con el archivo iconify-icon.js y el uso de IntersectionObserver para renderizar o remover iconos dependiendo de si están visibles en pantalla.

El síntoma

El problema aparece únicamente en páginas con altura considerable, donde era necesario hacer scroll para revisar el contenido. Al avanzar hacia abajo, llegaba un punto en el que la pantalla se quedaba “atorada” y comenzaba a “saltar”, como si intentara regresar a una posición anterior.

Después de realizar un rastreo y de varias pruebas, el comportamiento desaparecía al quitar el archivo:

<script src="/assets/js/iconify-icon.js"></script>

Esto permitió confirmar que el problema no estaba en Bootstrap, DataTables, CSS personalizado ni en el layout principal, sino en la forma en que Iconify estaba administrando los iconos durante el scroll.

Por qué sucede

Algunas versiones de iconify-icon.js usan IntersectionObserver para detectar si un icono está visible dentro del viewport. Cuando el icono sale de pantalla, el script puede remover el SVG renderizado; cuando vuelve a entrar, lo renderiza nuevamente.

El comportamiento problemático se encuentra en una lógica similar a esta:

    this._observer = new IntersectionObserver((entries) => {
        const isVisible = entries.some((entry) => entry.isIntersecting);

        if (isVisible !== this._visible) {
            this._visible = isVisible;
            this._forceRender();
        }
    });
    

Y dentro de _forceRender() puede existir una lógica que elimina el icono del Shadow DOM cuando ya no está visible:

    _forceRender() {
        if (this._visible) {
            this._queueCheck();
        } else {
            const element = getRenderedIcon(this._shadowRoot);

            if (element) {
                this._shadowRoot.removeChild(element);
            }
        }
    }

En teoría, esto busca optimizar el rendimiento. Pero en páginas largas, con muchos iconos y contenido dinámico, puede causar el efecto contrario: cada vez que el usuario hace scroll, el script modifica el DOM, el navegador recalcula layout, el observer vuelve a detectar cambios y se puede producir un ciclo de renderizado/recalculo.

La solución aplicada

La solución fue desactivar ese comportamiento de observación de visibilidad y mantener los iconos renderizados de forma estable. Esto evita que Iconify quite y vuelva a insertar SVGs durante el scroll.

Antes de modificar el archivo, es recomendable hacer un respaldo:

cp public/assets/js/iconify-icon.js public/assets/js/iconify-icon.backup.js

Después, se pueden aplicar los siguientes ajustes dentro del archivo iconify-icon.js.

1. Ajustar connectedCallback()

Busca una función similar a esta:

connectedCallback() {
    this._connected = true;
    this.startObserver();
}

Y cámbiala por:

connectedCallback() {
    this._connected = true;
    this._visible = true;
    this._queueCheck();
}

Con este cambio, al conectar el elemento al DOM, el icono se considera visible y se renderiza sin iniciar el observer.

2. Corregir la lógica de noobserver

En algunas versiones, el manejo del atributo noobserver puede estar invertido. Si encuentras algo similar a esto:

case "noobserver":
    this.hasAttribute("noobserver") ? this.startObserver() : this.stopObserver();
    break;

Cámbialo por:

case "noobserver":
    this.hasAttribute("noobserver") ? this.stopObserver() : this.startObserver();
    break;

Esto hace que, si el elemento tiene el atributo noobserver, el observer se detenga en lugar de iniciarse.

3. Reemplazar startObserver()

Busca el método startObserver(). Normalmente contiene una instancia de IntersectionObserver, similar a esta:

startObserver() {
    if (!this._observer && !this.hasAttribute("noobserver")) {
        try {
            this._observer = new IntersectionObserver((entries) => {
                const isVisible = entries.some((entry) => entry.isIntersecting);

                if (isVisible !== this._visible) {
                    this._visible = isVisible;
                    this._forceRender();
                }
            });

            this._observer.observe(this);
        } catch (error) {
            if (this._observer) {
                try {
                    this._observer.disconnect();
                } catch (error) {}

                this._observer = null;
            }
        }
    }
}

Reemplázalo por una versión simple:

startObserver() {
    this._visible = true;
    this._queueCheck();
}

Este es el cambio principal. El icono se mantiene renderizado y ya no se elimina o reconstruye durante el scroll.

4. Ajustar _forceRender()

Busca una función similar a esta:

_forceRender() {
    if (this._visible) {
        this._queueCheck();
    } else {
        const element = getRenderedIcon(this._shadowRoot);

        if (element) {
            this._shadowRoot.removeChild(element);
        }
    }
}

Y cámbiala por:

_forceRender() {
    this._visible = true;
    this._queueCheck();
}

Con esto se evita que cualquier flujo interno vuelva a remover el SVG del Shadow DOM.

Cómo evitar problemas de caché

Después de modificar el archivo, es importante forzar al navegador a cargar la nueva versión. Una forma sencilla es agregar un parámetro de versión al script:

<script src="/assets/js/iconify-icon.js?v=1.0.2"></script>

También puedes limpiar caché manualmente o compilar nuevamente los assets si el archivo forma parte de un proceso de build.

Alternativa: usar un patch separado

Si no quieres modificar directamente la librería, puedes crear un archivo adicional que se cargue después de iconify-icon.js. Esta opción es útil cuando el archivo original pertenece a una plantilla o paquete de terceros.

<script src="/assets/js/iconify-icon.js"></script>
<script src="/assets/js/iconify-icon-noobserver-patch.js"></script>

Sin embargo, si el problema está dentro de la clase registrada como Web Component, modificar directamente el método dentro del archivo original suele ser más estable, siempre que se haga con respaldo y control de versiones.

Cuándo conviene aplicar esta solución

Este ajuste puede ser útil cuando se cumplen varias de estas condiciones:

  • La página tiene muchos iconos generados con <iconify-icon>.
  • El problema aparece únicamente en páginas largas con scroll.
  • El salto desaparece al quitar iconify-icon.js.
  • No existen errores claros en consola.
  • El navegador muestra actividad alta de renderizado o recalculado de estilos.

Recomendaciones finales

Antes de modificar una librería de terceros, conviene confirmar que realmente es la causa del problema. Para hacerlo puedes probar lo siguiente:

  • Desactivar temporalmente el script sospechoso.
  • Probar la misma página con pocos iconos.
  • Revisar si el problema aparece solo al hacer scroll.
  • Validar que no existan scripts duplicados.
  • Hacer respaldo del archivo original antes de editarlo.

En este caso, la solución fue mantener los iconos renderizados y desactivar la observación de visibilidad con IntersectionObserver. Con ello se evita que el navegador entre en un ciclo de recalculado al hacer scroll y la interfaz vuelve a comportarse de forma estable.

Conclusión

Los bugs de scroll pueden ser difíciles de detectar porque no siempre generan errores en consola. Muchas veces el problema no está en el CSS, sino en scripts que modifican el DOM mientras el usuario navega por la página.

Cuando una librería intenta optimizar el renderizado quitando y agregando elementos durante el scroll, puede terminar provocando saltos visuales, congelamientos o recalculados constantes. En interfaces administrativas con muchos iconos, desactivar ese comportamiento puede ser una solución práctica y efectiva.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *