Si tu as ouvert une heatmap Matomo et trouve ton header tamponné deux, trois, parfois six fois sur la page comme un cachet de la poste, ou qu'une copie de ce header est figee en plein milieu en recouvrant le contenu en dessous, sache que les clics eux-memes sont corrects. Matomo les a enregistres aux bonnes coordonnees. Ce qui est casse, c'est la capture d'ecran dessous. Ton header sticky ne s'est pas rendu comme il le fait dans un vrai navigateur, alors il se repete a chaque position de scroll ou se colle a l'endroit ou le renderer regardait par hasard. Dans les deux cas, tous les clics en dessous se retrouvent plottes sur des liens de nav, et un gros morceau de ta vraie heatmap devient une zone morte.
La correction la plus rapide, c'est une extension Chrome gratuite qu'on maintient, qui s'appelle Matomo Heatmap Helper. Elle detecte tous les headers en position: fixed et position: sticky sur la page, les passe en position: relative avec un placeholder de meme hauteur pendant la capture, puis les restaure apres. La suite de cet article explique comment faire la meme chose sans extension, plus un pattern CSS permanent qui vaut le coup d'etre embarque si tu captures des heatmaps regulierement.
Comment corriger ca sans l'extension
Il y a un snippet de console qui regle le probleme juste avant chaque capture, et une branche CSS permanente que tu peux embarquer dans le site. Prends celui qui correspond a tes contraintes.
Trouver tous les headers fixes et sticky de la page
Avant de changer quoi que ce soit, decouvre ce qui est vraiment ancre. Ouvre la page dans Chrome, appuie sur F12 pour ouvrir les DevTools, passe dans la Console, et colle ca :
// 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));Tout ce qui affiche fixed ou sticky est un suspect. Sur la plupart des sites marketing, tu en trouveras plus d'un. Le header principal est en fixed, la barre de cookies est en fixed, un bouton "retour en haut" est en fixed, une sous-nav secondaire est en sticky. Ils s'empilent tous et cassent la capture chacun a leur facon.
Desancrer tous les headers juste avant la capture
Colle ca avant de declencher la capture de heatmap. Ca fait la meme chose que le sticky-header-fixer de l'extension : passe chaque header fixe ou sticky en position: relative, et pour les headers en fixed (qui etaient detaches du flux du document), insere un placeholder invisible de meme hauteur pour que la position de scroll et les coordonnees de clic ne bougent pas.
// 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.');
})();Lance-le, regarde le compte, declenche la capture. S'il affiche Unstuck 0 headers alors que tu vois encore un header qui se repete sur la page, c'est que la liste de selecteurs ci-dessus ne couvre pas la classe que ton header utilise vraiment. Inspecte l'element, trouve l'element englobant avec position: fixed, et ajoute son selecteur a SELECTOR.
Ou cacher le header completement pendant la capture
Une heatmap sur un header sticky ne t'apprend presque rien de toute facon. Les visiteurs scrollent, le header se deplace avec eux, et chaque clic sur la nav atterrit a une coordonnee de page Y differente. Le resultat est une heatmap de la nav qui s'etale sur toute la hauteur de la page. Si le header n'est pas ce que tu cherches a analyser, le plus simple est de le retirer completement du cadre :
// Hides every header during the capture
document.querySelectorAll('header, nav, [role=banner], .navbar, .sticky')
.forEach(el => el.style.display = 'none');La capture revient comme si le header n'etait pas la. Les vrais clics en dessous atterrissent quand meme sur les bons elements, parce que les coordonnees de clic ne changent pas. Tu perds juste les donnees de heatmap (surtout inutiles) sur la nav elle-meme.
Correction CSS permanente activee uniquement en mode capture
Si tu captures des heatmaps sur les memes pages en boucle, le workflow copier-coller en console finit par lasser. Mieux vaut une branche CSS qui ne s'active que quand tu visites l'URL avec un parametre comme ?matomo_heatmap=1. Ajoute une classe a <html> quand le parametre est present :
// Add to your site (or your tag manager)
if (new URLSearchParams(location.search).has('matomo_heatmap')) {
document.documentElement.classList.add('heatmap-mode');
}Puis embarque le CSS qui desancre tous les headers dans cette classe :
/* 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;
}Le workflow de capture se resume alors a : visiter ?matomo_heatmap=1, declencher la heatmap, c'est fait. Pas de copier-coller en console, pas de risque d'oublier. Le bemo, c'est que l'astuce du placeholder (qui preserve la mise en page originale pour les headers en position: fixed) est difficile a exprimer en CSS pur, donc la page peut decaler legerement quand tu actives la classe. Sur la plupart des sites, c'est acceptable. Ce qui compte, c'est la donnee de clic, et visuellement ca reste suffisamment proche de la vraie mise en page pour etre lisible.
Pourquoi Matomo ne peut pas rendre les headers sticky correctement
Le renderer de heatmap de Matomo prend le DOM serialise et le rasterise en une seule grande image, toute la hauteur de la page du haut de <body> jusqu'en bas. Il n'y a pas de scroll dans ce pipeline. Il n'y a pas de viewport comme un vrai navigateur en a un. C'est une grande canvas unique.
Un header en position: fixed est detache du flux du document et ancre au viewport courant. Quand le renderer produit une grande canvas sans viewport, le resultat est indefini. Certains renderers tamponnent le header a chaque etape de scroll qu'ils auraient faite, donc il se repete tous les 600 a 1 000 pixels. D'autres le verrouillent a une coordonnee Y arbitraire et s'en tiennent la. position: sticky tombe dans le meme probleme par l'autre bout : la limite sticky est definie par rapport a un ancetre qui scroll, et il n'y a pas d'ancetre qui scroll quand la page se rend en une seule image.
Dans les deux cas, le rendu visuel est faux, et les clics (qui ont ete enregistres par rapport aux vraies coordonnees de la page par le tracker dans le navigateur) se retrouvent plottes sur des pixels de header qui ne sont pas au bon endroit.
Ce qui echoue exactement
Quelques patterns qu'on croise tout le temps :
- Le header principal du site est en
position: fixed, et le renderer le tamponne tous les 600 a 1 000 pixels sur la page. Le hero, les arguments produit, les temoignages, chacun d'eux a la meme nav qui flotte en haut. - Une barre de consentement aux cookies est en
position: fixeden bas du viewport. Elle apparait a mi-page dans la capture, recouvrant le tableau de tarifs ou la grille de produits. - Un bouton "retour en haut" ou un widget de chat flottant est en
position: fixeden bas a droite. Il apparait tamponne sur chaque section, avec des marqueurs de clic un peu partout sur du contenu qui n'a rien a voir avec le bouton. - Une sous-nav utilise
position: stickyavec unz-indexeleve et se fige a une position de scroll arbitraire par le renderer. Chaque clic sous cette coordonnee Y, n'importe ou sur la page, se retrouve plotte par-dessus la sous-nav. - Un modal ou une banniere que le visiteur a ferme avant de cliquer quoi que ce soit apparait quand meme dans la capture, parce que l'etat de fermeture vivait en JavaScript et que le renderer a demarre a froid depuis le DOM serialise.
Si ta heatmap montre une nav qui se repete, des overlays figes, ou des coordonnees de clic plottes sur des pixels de header, tu as au moins un de ces cas. Sur n'importe quel site avec un header principal et une barre de cookies, tu en as deux.
C'est aussi la meme famille de probleme qui casse les polices, les images et les conteneurs avec scroll dans la meme capture. On a ecrit un article plus long sur les captures de heatmaps Matomo cassees qui parcourt le reste, avec des articles separes sur les polices et les images puisque celles-ci reviennent presque aussi souvent.
Ce qu'on ferait concrètement
Si tu vas capturer des heatmaps sur les memes pages plus de deux ou trois fois, embarque la branche CSS ?matomo_heatmap=1. C'est quelques lignes de CSS, tu le configures une fois, et le workflow apres c'est juste un parametre d'URL. Le leger decalage de mise en page lie a l'absence de placeholders est presque toujours negligeable.
Si tu ne controles pas les templates ou le CSS, colle le snippet de desancrage avant chaque capture. Pas elegant, mais ca marche sur chaque page ou tu peux ouvrir les DevTools.
Pour les missions clients ou on capture sur beaucoup de pages et qu'on ne veut pas embarquer du code dans la stack de quelqu'un d'autre, l'extension Chrome Matomo Heatmap Helper fait tout ca automatiquement. Elle detecte chaque header fixe ou sticky, les passe en position: relative avec un placeholder de meme hauteur, execute la capture, et restaure tout apres. La meme logique couvre les polices, les images et les conteneurs avec scroll en une seule passe. Gratuit, open source, code sur GitHub.
Martez est le projet plus large dont l'extension est issue. Il connecte Matomo avec Meta Ads et Google Ads pour que le ROAS, la CLV et l'attribution soient au meme endroit que tes web analytics, plutot que dans un tableur separe. C'est en beta privee. Rejoins la liste d'attente si ca te parle.
La plupart du temps, c'est a une branche CSS pres. Ca vaut le coup de le faire une fois.