Por que el mapa de calor de Matomo se corta a partir de cierto punto (y como arreglarlo)

Las capturas de pantalla de los mapas de calor de Matomo se vuelven negras o en blanco por debajo de cierta linea cuando una barra lateral, un panel con pestanas o cualquier contenedor con altura limitada recorta su overflow durante la captura. Los clics se siguen registrando, solo que caen sobre espacio vacio. Aqui te explicamos por que pasa y como lo solucionamos.

Si has abierto un mapa de calor de Matomo y has visto que todo lo que hay por debajo de cierta linea queda reemplazado por un rectangulo negro, con la barra lateral colapsada a la altura que tenia en pantalla y los marcadores de clics apilados inutilmente sobre color plano, los clics en si estan bien. Matomo los registro en las coordenadas correctas. Lo que falla es la captura de pantalla que hay debajo. Algun contenedor de tu pagina en vivo que limitaba su propia altura y dejaba que el contenido hiciera overflow dentro de una region con scroll (la barra lateral, el panel de pestanas, el desplegable con la FAQ) no metio su contenido completo en el DOM capturado. El contenedor mantiene su altura limitada durante el paso de captura, y Matomo pinta como espacio vacio todo lo que queda mas alla.

La solucion mas rapida es una extension gratuita de Chrome que mantenemos nosotros, Matomo Heatmap Helper. Justo antes de cada captura, recorre todos los elementos de la pagina, localiza los que tienen el scrollHeight mayor que el clientHeight, y reescribe su height y max-height para que la captura vea el contenido completo. Despues restaura los valores originales. El resto del post explica como hacer lo mismo sin una extension, mas un patron CSS permanente que merece la pena tener en produccion.

Como arreglarlo sin la extension

Hay un snippet de consola que soluciona el problema justo antes de cada captura, y un patron CSS activado por query param que puedes desplegar en produccion para mantener intacta la UX de tus usuarios reales. Usa el que mejor encaje con tu stack.

Localiza los elementos que fallan

Antes de cambiar nada, encuentra los contenedores que estan recortando el contenido. Abre la pagina en Chrome, pulsa F12 para abrir DevTools, ve a la consola, pega esto y dale a Enter. Todo lo que aparezca con una diferencia de mas de cinco pixeles es candidato:

js
// Pega esto en la consola del navegador en la pagina afectada.
// Lista todos los elementos cuyo contenido queda recortado por una altura limitada.
Array.from(document.querySelectorAll('*'))
  .filter(el => el.scrollHeight > el.clientHeight + 1)
  .forEach(el => console.log(el.scrollHeight - el.clientHeight, el));

Las diferencias pequenas (uno o dos pixeles) suelen ser redondeo de subpixeles y no son tu problema. Lo que reporta cientos o miles de pixeles de contenido recortado es lo que buscas. Ahi es donde el mapa de calor se oscurece.

El arreglo rapido: expande a la fuerza todos los contenedores recortados

Este es el snippet que pegamos en la consola del navegador justo antes de lanzar la captura del mapa de calor de Matomo. Recorre todos los elementos de la pagina, expande los que tienen contenido desbordado y desactiva sus reglas de overflow para que el contenido completo sea visible. Cuando Matomo serializa el DOM, las secciones recortadas ya estan desplegadas.

js
// Pega en la consola. Expande todos los contenedores con overflow oculto.
document.querySelectorAll('*').forEach(el => {
  if (el.scrollHeight > el.clientHeight + 1) {
    el.style.height = el.scrollHeight + 'px';
    el.style.minHeight = el.scrollHeight + 'px';
    el.style.maxHeight = 'none';
    el.style.overflow = 'visible';
  }
});

Para confirmar que no queda nada recortado antes de lanzar la captura:

js
// Devuelve el numero de elementos que siguen recortando su contenido. Deberia ser cero.
Array.from(document.querySelectorAll('*'))
  .filter(el => el.scrollHeight > el.clientHeight + 1).length;

Si es cero, puedes continuar. Si no, los elementos restantes suelen estar dentro de shadow roots o iframes de origen cruzado a los que el snippet no puede acceder. Mas abajo hablamos de eso.

Este es el camino rapido. Funciona para capturas puntuales y para los ciclos de revision en los que necesitas una captura limpia ahora sin esperar a un cambio de codigo. Para paginas que vayas a recapturar con regularidad, despliega el arreglo permanente que viene a continuacion.

El arreglo permanente: una hoja de estilos solo para el mapa de calor

La clave es mantener intacta la UX de tus usuarios reales (barras laterales fijas, paneles de pestanas con scroll, todo lo que existe por una razon) mientras el paso del mapa de calor ve el contenido completo. Una clase CSS activada por query param es la forma mas sencilla de conseguirlo:

css
/* Edita los selectores para que coincidan con los contenedores de tu sitio */
html.heatmap-capture .sidebar,
html.heatmap-capture .tab-panel,
html.heatmap-capture [data-scrolls] {
  max-height: none !important;
  height: auto !important;
  min-height: 0 !important;
  overflow: visible !important;
}
js
// Anade a tu sitio. Activa el override cuando visitas con ?heatmap=1
if (new URLSearchParams(location.search).get('heatmap') === '1') {
  document.documentElement.classList.add('heatmap-capture');
}

Ahora visitar https://tu-sitio.com/pagina?heatmap=1 pone la pagina en modo de captura. Los usuarios reales acceden sin el param y conservan su scroll normal. El mismo sitio, dos layouts, un query param entre ellos.

Si no quieres exponer el activador en la URL, cambia la comprobacion del param por una cookie activada desde una ruta solo de administrador, o por una comprobacion del User-Agent contra el fetcher de capturas de pantalla de Matomo (busca en tus logs de acceso el string exacto de UA que usa el renderizador de Matomo, y aplica el filtro en el servidor). Misma idea, diferente punto de control.

El compromiso

No despliegues max-height: none a todos los visitantes. Las barras laterales fijas, los paneles de pestanas con scroll y los drawers con altura limitada existen por una razon: mantienen la navegacion visible, permiten que los paneles coexistan con el resto del layout y evitan que un acordeon de 4.000 pixeles de alto se apodere de la pagina. El punto de activar el override por query param es precisamente ese: mantener produccion tal como la esperan los usuarios reales y cambiar el layout solo cuando captures.

Flujo de trabajo probado: abre la pagina en una ventana de incognito con ?heatmap=1, haz clic para registrar la sesion del mapa de calor, deja la pestana abierta hasta que se lance la captura de Matomo. Los usuarios reales en la misma pagina con sus sesiones normales nunca ven el override.

Por que Matomo no puede capturar a partir del corte

La captura de pantalla de Matomo es un proceso en dos pasos. Primero, el tracker serializa tu DOM en vivo a HTML y lo envia. Despues, tu servidor de Matomo re-renderiza ese HTML para producir la captura que ves en la vista del mapa de calor. El renderizador pinta la pagina exactamente como la describe el DOM serializado. Si un contenedor en ese DOM tiene height: 600px y overflow: hidden con contenido que en realidad mide 2.000 pixeles de alto, solo los 600 pixeles superiores aparecen en la imagen. Todo lo que queda debajo se recorta en el limite del contenedor, y el canvas del mapa de calor pinta el area mas alla como espacio vacio (negro, blanco o transparente segun lo que haya por debajo).

Las coordenadas de los clics se registran a nivel de documento, asi que sobreviven al recorte sin problema. Los pixeles a los que apuntaban no.

Que esta fallando realmente

Algunos patrones que nos encontramos una y otra vez:

  • Barras laterales con max-height: calc(100vh - header) y overflow-y: auto, habituales en mega-nav, filtros facetados y paneles de chat. La barra lateral es mas alta que el viewport en la pagina en vivo. La captura solo ve el fragmento del alto del viewport.
  • Paneles con pestanas donde cada [role=tabpanel] tiene su propio contenedor con scroll, comportamiento por defecto en Bootstrap, Tailwind UI, MUI y la mayoria de bibliotecas de componentes. La pestana activa se recorta a su altura limitada y las pestanas inactivas no son visibles en absoluto.
  • Secciones estilo drawer que no son modales de verdad: slide-overs, bandejas de filtros expandidas, paneles de carrito inline. Cualquier cosa limitada a 100vh o a un alto en pixeles fijo a proposito. El contenedor mantiene su limite durante la captura.
  • Tarjetas de dashboard con scrollers internos, tiles de altura fija donde el contenido desborda dentro de un div con overflow-y: auto. Matomo captura la tile con sus dimensiones externas y todo lo que queda por encima del fragmento visible del scroller interno desaparece.
  • Bibliotecas de componentes que lo incorporan por diseno: ScrollArea de shadcn/ui, el Drawer de MUI con scroll interno, las listas de combobox de Tailwind UI, los paneles de pestanas de Headless UI. Conviene saberlo cuando auditas la pagina; el elemento que falla esta muchas veces tres capas dentro de un wrapper que no escribiste tu.
  • Iframes con dimensiones limitadas. El propio DOM del iframe se recorta al tamano externo del iframe, y Matomo no puede acceder al interior de iframes de origen cruzado de todas formas.

Si tu mapa de calor se oscurece a partir de cierta linea, estas topandote con al menos uno de estos casos. La mayoria de paginas con una barra lateral de contenido tienen dos a la vez.

Esta es ademas la misma familia de problema que rompe fuentes, imagenes y encabezados sticky en la misma captura. Hemos escrito un post mas largo sobre capturas de mapas de calor de Matomo rotas que repasa el resto, mas posts separados sobre por que las fuentes no cargan en tu mapa de calor de Matomo y por que las imagenes no cargan en tu mapa de calor de Matomo.

Que hariamos nosotros

Si controlas las plantillas, despliega la hoja de estilos con el filtro. Son un punado de selectores y una comprobacion de query param, y se queda en produccion mientras uses mapas de calor de Matomo. Cinco minutos de trabajo, para siempre.

Si no puedes, el snippet de consola cubre el mismo terreno para capturas puntuales. La pega es que tienes que acordarte de pegarlo antes de cada captura.

Si ninguna de las dos opciones es viable (plantillas bloqueadas por el CMS, widgets de terceros que no controlas, cualquier situacion en la que no puedas anadir una regla CSS ni una etiqueta script), la extension de Chrome Matomo Heatmap Helper es lo que usamos en sitios de clientes donde no podemos tocar el stack. Ejecuta la misma reescritura de scrollHeight/maxHeight durante la captura y restaura los originales despues, mas arreglos equivalentes para fuentes, imagenes y encabezados sticky. Gratuita, de codigo abierto, codigo en GitHub.

Martez es el proyecto mas amplio del que surgio la extension. Conecta Matomo con Meta Ads y Google Ads para que el ROAS, el CLV y la atribucion esten junto a tu analitica web en lugar de en una hoja de calculo aparte. Esta en beta privada. Unete a la lista de espera si te resulta relevante.

Unos pocos selectores y un query param. Vale la pena hacerlo una vez.

Por que el mapa de calor de Matomo se corta a partir de cierto punto (y como arreglarlo) - Martez Blog