Si has abierto un heatmap de Matomo y has encontrado tu encabezado estampado por la pagina dos, tres, a veces seis veces como un sello de correos, o una copia de el congelada en mitad de la pagina tapando el contenido que hay debajo, los clics en si estan bien. Matomo los registro en las coordenadas correctas. Lo que esta roto es la captura de pantalla que hay debajo. Tu encabezado sticky no se renderizo como lo hace en un navegador, asi que o se repite en cada posicion de scroll o se ancla a donde el renderer se encontraba mirando en ese momento. De cualquier manera, cada clic que queda debajo se traza encima de los enlaces de navegacion, y un trozo de tu heatmap real se convierte en una gran zona muerta.
La solucion mas rapida es una extension gratuita de Chrome que mantenemos nosotros llamada Matomo Heatmap Helper. Detecta todos los encabezados fixed y sticky de la pagina, los cambia a position: relative con un placeholder de la misma altura durante la captura, y los restaura despues. El resto del post explica como hacer lo mismo sin una extension, mas un patron CSS permanente que vale la pena incluir si vas a capturar heatmaps de forma habitual.
Como arreglarlo sin la extension
Hay un snippet de consola que tapa el problema justo antes de cada captura, y una rama CSS permanente que puedes incluir en el sitio. Elige la que mejor encaje con las limitaciones que tienes.
Encuentra todos los encabezados fixed y sticky de la pagina
Antes de cambiar nada, averigua que esta anclado de verdad. Abre la pagina en Chrome, pulsa F12 para abrir DevTools, cambia a la pestana de Consola y pega:
// Logs every header-like element and its computed position value
document.querySelectorAll('header, nav, [role=banner], [role=navigation], .header, .navbar, .nav, .sticky')
.forEach(el => console.log(getComputedStyle(el).position, el));Todo lo que diga fixed o sticky es sospechoso. En la mayoria de sitios de marketing vas a encontrar mas de uno. El encabezado principal es fixed, la barra de cookies es fixed, un boton de "volver arriba" es fixed, un sub-nav secundario es sticky. Todos se acumulan y todos rompen la captura a su manera.
Quita el sticky a todos los encabezados justo antes de la captura
Pega esto antes de lanzar la captura del heatmap. Hace lo mismo que el sticky-header-fixer de la extension: cambia cada encabezado fixed y sticky a position: relative, e inserta un placeholder invisible de la misma altura para los encabezados fixed (que estaban desconectados del flujo del documento) para que la posicion de scroll y las coordenadas de los clics no se desplacen.
// Paste into the browser console. Unsticks every fixed/sticky header so it
// doesn't repeat or float across the heatmap, and inserts a placeholder
// with the same height/width to preserve the rest of the layout.
(() => {
const SELECTOR = 'header, nav, [role=banner], [role=navigation], .header, .navbar, .nav, .sticky';
let unstuck = 0;
document.querySelectorAll(SELECTOR).forEach(el => {
const cs = getComputedStyle(el);
if (cs.position !== 'fixed' && cs.position !== 'sticky') return;
// Capture original dimensions before changing position
const rect = el.getBoundingClientRect();
// Insert invisible placeholder for fixed (sticky already takes layout space)
if (cs.position === 'fixed' && el.parentElement) {
const ph = document.createElement('div');
ph.style.height = rect.height + 'px';
ph.style.width = rect.width + 'px';
ph.style.visibility = 'hidden';
ph.dataset.heatmapPlaceholder = 'true';
el.parentElement.insertBefore(ph, el);
}
// Convert to relative so it joins document flow and stops repeating
el.style.position = 'relative';
el.style.top = 'auto';
el.style.bottom = 'auto';
el.style.left = 'auto';
el.style.right = 'auto';
el.style.zIndex = 'auto';
el.style.width = 'auto';
unstuck++;
});
console.log(`Unstuck ${unstuck} headers. Now trigger your Matomo heatmap capture.`);
console.log('To revert: location.reload(), or remove the placeholders by hand.');
})();Ejecutalo, mira el contador, lanza la captura. Si dice Unstuck 0 headers y aun puedes ver un encabezado que se repite en la pagina, es que la lista de selectores de arriba no incluye la clase que usa tu encabezado. Inspecciona el elemento, encuentra el elemento que tiene position: fixed, y añade su selector a SELECTOR.
O simplemente oculta el encabezado durante la captura
Un heatmap en un encabezado sticky raramente te cuenta algo util de todas formas. Los visitantes hacen scroll, el encabezado se mueve con ellos, y cada clic en la navegacion aterriza en una coordenada Y de pagina diferente. El resultado es un heatmap de la nav que queda embadurnado por toda la altura de la pagina. Si el encabezado no es lo que quieres analizar, lo mas sencillo es quitarlo directamente de la ecuacion:
// Hides every header during the capture
document.querySelectorAll('header, nav, [role=banner], .navbar, .sticky')
.forEach(el => el.style.display = 'none');La captura vuelve como si el encabezado no existiera. Los clics reales que hay debajo siguen cayendo en los elementos correctos, porque las coordenadas no cambian. Solo pierdes los datos del heatmap de la propia nav (que tampoco aportan mucho).
Arreglo CSS permanente acotado al modo de captura
Si capturas heatmaps en las mismas paginas repetidamente, el flujo de pegar snippets en la consola cansa. Mejor una rama CSS que solo se activa cuando visitas la URL con una flag como ?matomo_heatmap=1. Añade una clase a <html> cuando la flag este presente:
// Add to your site (or your tag manager)
if (new URLSearchParams(location.search).has('matomo_heatmap')) {
document.documentElement.classList.add('heatmap-mode');
}Luego incluye CSS que quite el sticky a todos los encabezados dentro de esa clase:
/* Edit selectors to match your site's header markup */
html.heatmap-mode header,
html.heatmap-mode nav,
html.heatmap-mode [role=banner],
html.heatmap-mode .navbar {
position: relative !important;
top: auto !important;
left: auto !important;
right: auto !important;
bottom: auto !important;
width: auto !important;
z-index: auto !important;
}El flujo de captura se reduce a: visitar ?matomo_heatmap=1, lanzar el heatmap, listo. Sin pegar nada en la consola, sin riesgo de olvidarlo. El inconveniente es que el truco del placeholder (para preservar el layout original en los encabezados position: fixed) es dificil de expresar en CSS puro, asi que la pagina puede desplazarse un poco al activar la clase. Para la mayoria de sitios eso esta bien. Lo que importa son los datos de clics, y el aspecto visual se mantiene lo suficientemente cerca del layout real como para ser legible.
Por que Matomo no puede renderizar los encabezados sticky correctamente
El renderer de heatmaps de Matomo toma el DOM serializado y lo rasteriza como una imagen alta unica, la altura completa de la pagina desde el inicio de <body> hasta el final. No hay scroll en ese proceso. No hay viewport como lo tiene un navegador real. Es un canvas gigante.
Un encabezado con position: fixed esta desconectado del flujo del documento y anclado a cualquier viewport que haya en ese momento. Cuando el renderer produce un canvas alto sin viewport, el resultado es indefinido. Algunos renderers estampan el encabezado en cada paso de scroll que habrian hecho, asi que se repite cada 600 a 1.000 pixeles. Otros lo bloquean en una coordenada Y arbitraria y ya esta. position: sticky se topa con el mismo problema desde la direccion opuesta: el limite del sticky se define respecto a un ancestro con scroll, y no hay ancestro con scroll cuando la pagina se renderiza como una imagen.
De cualquier manera, la salida visual es erronea, y los clics (que fueron registrados contra coordenadas reales de pagina por el tracker en el navegador) acaban trazados encima de pixeles del encabezado que no estan en el lugar correcto.
Que falla exactamente
Algunos patrones que nos encontramos una y otra vez:
- El encabezado principal del sitio es
position: fixed, y el renderer lo estampa cada 600 a 1.000 pixeles hacia abajo. El hero, las propuestas de valor, los testimonios, todos tienen la misma nav flotando encima. - Una barra de consentimiento de cookies esta en
position: fixeden la parte inferior del viewport. Aparece a mitad de la captura, tapando la tabla de precios o la cuadricula de productos. - Un boton de "volver arriba" o un widget de chat flotante esta en
position: fixeden la esquina inferior derecha. Aparece estampado encima de cada seccion, dejando marcadores de clics por encima de contenido que no tiene nada que ver con el boton. - Un sub-nav usa
position: stickycon unz-indexalto y el renderer lo bloquea en una posicion de scroll arbitraria. Cada clic por debajo de esa coordenada Y, en cualquier punto de la pagina, se traza encima del sub-nav. - Un modal o banner que el visitante cerro antes de hacer clic en nada sigue apareciendo en la captura, porque el estado de cierre vivia en JS y el renderer empezo desde cero con el DOM serializado.
Si tu heatmap muestra la nav repetida, superposiciones congeladas o coordenadas de clics trazadas encima de pixeles del encabezado, estas teniendo al menos uno de estos problemas. En cualquier sitio que tenga tanto un encabezado principal como una barra de cookies, tienes dos.
Este es tambien el mismo tipo de problema que rompe fuentes, imagenes y contenedores con scroll en la misma captura. Hemos escrito un post mas largo sobre las capturas de heatmaps de Matomo rotas que repasa el resto, mas posts separados sobre fuentes e imagenes porque esos casos aparecen casi con la misma frecuencia.
Que hariamos nosotros
Si vas a capturar heatmaps en las mismas paginas mas de un par de veces, incluye la rama CSS de ?matomo_heatmap=1. Son unas pocas lineas de CSS, lo configuras una vez, y el flujo de trabajo despues es solo un parametro de query. El pequeno desplazamiento del layout por saltarse los placeholders casi siempre esta bien.
Si no controlas las plantillas ni el CSS, pega el snippet de unstick antes de cada captura. No es bonito, pero funciona en cualquier pagina que puedas abrir con DevTools.
Para trabajo con clientes donde capturamos en muchas paginas y no queremos meter codigo en la infraestructura de otros, la extension Matomo Heatmap Helper para Chrome hace esto de forma automatica. Detecta cada encabezado fixed y sticky, los cambia a position: relative con un placeholder de la misma altura, ejecuta la captura y lo restaura todo despues. La misma logica cubre fuentes, imagenes y contenedores con scroll de una sola vez. Gratis, de codigo abierto, codigo en GitHub.
Martez es el proyecto mas amplio del que salio la extension. Conecta Matomo con Meta Ads y Google Ads para que el ROAS, el CLV y la atribucion esten junto a tus analisis web en lugar de en una hoja de calculo aparte. Esta en beta privada. Unete a la lista de espera si te interesa.
La mayoria de las veces, esto esta a una rama CSS de distancia. Merece la pena hacerlo una vez.