Waarom delen van je pagina worden afgesneden in je Matomo heatmap (en hoe je dat repareert)

Matomo heatmap-screenshots knippen stilzwijgend alles af wat zich binnen scrollcontainers en overflow:hidden-wrappers bevindt. Sidebars, modals en 'scrollytelling'-layouts verdwijnen voorbij hun zichtbare rand, en klikken landen op wat er op dat moment te zien was tijdens het vastleggen. Dit is waarom het gebeurt en hoe we ermee omgaan.

Als je een Matomo heatmap hebt geopend en je sidebar afgekapt zag, de onderste helft van een modal miste, of klikmarkeringen zag zweven over content die er niet bij leek te horen — de klikken zelf kloppen. Matomo heeft ze op de juiste coördinaten opgeslagen. Wat kapot is, is de screenshot eronder. Ergens op de pagina staat overflow: hidden, overflow: auto of overflow: scroll ingesteld op een wrapper, en Matomo's renderer behandelt alles voorbij de zichtbare rand van die wrapper alsof het er niet is. De heatmap toont je dus een uitsnede van de pagina in plaats van de pagina die je bezoekers werkelijk zagen.

De snelste fix is een gratis Chrome-extensie die we onderhouden: Matomo Heatmap Helper. Die neutraliseert overflow-containers vlak voor elke capture, zorgt dat de renderer het volledige document ziet, en herstelt de oorspronkelijke stijlen daarna. De rest van dit artikel laat zien hoe je hetzelfde doet zonder extensie, plus een paar permanente fixes die het waard zijn om te shippen.

Hoe je het zonder de extensie repareert

Er is een console-snippet die het probleem oplost vlak voor elke capture, en een paar permanente fixes die je met de site kunt meesturen. Kies wat past bij de beperkingen waar je mee werkt.

Zoek alle overflow-beperkte elementen op de pagina

Voordat je iets wijzigt, zoek je uit welke wrappers er knippen. Open de pagina in Chrome, druk op F12 om DevTools te openen, ga naar de Console en plak dit:

js
// Lists every element that's currently clipping content
Array.from(document.querySelectorAll('*')).filter(el => {
  const s = getComputedStyle(el);
  return ['hidden','auto','scroll'].includes(s.overflow)
      || ['hidden','auto','scroll'].includes(s.overflowY);
});

Dat geeft je de exacte lijst van nodes die de Matomo-renderer als dozen met vaste afmetingen behandelt. De gebruikelijke verdachten zijn sidebars, modals, aangepaste scroll-wrappers rond <main>, en oude clearfix-wrappers met overflow: hidden om layoutredenen die al jaren niet meer relevant zijn.

De snelle fix: verwijder overflow-beperkingen via de console

Dit is de snippet die we in de browserconsole plakken vlak voordat we Matomo's heatmap-capture triggeren. Hij loopt langs elk element dat momenteel knipt en zet overflow en overflow-y op visible. Tegen de tijd dat Matomo de DOM serialiseert, verstopt niets zich meer achter een zichtbare rand.

js
// Paste into the browser console right before capture.
// Turns every overflow container on the page into 'visible'.
document.querySelectorAll('*').forEach(el => {
  const s = getComputedStyle(el);
  if (['hidden','auto','scroll'].includes(s.overflow) ||
      ['hidden','auto','scroll'].includes(s.overflowY)) {
    el.style.overflow = 'visible';
    el.style.overflowY = 'visible';
  }
});

De pagina ziet er even vreemd uit. Sidebars klappen uit tot hun volledige contenthoogte, modals strekken zich voorbij de rand van de viewport uit, en alles wat afhankelijk was van een container met vaste hoogte heeft die plotseling niet meer. Dat is prima. Je legt de heatmap vast, je browsert niet op de site. Refresh als je klaar bent.

Dit is de snelle weg. Het werkt voor eenmalige captures en reviewcycli waarbij je nu een schone screenshot nodig hebt en niet wilt wachten op een codewijziging. Voor alles wat je regelmatig opnieuw wilt vastleggen, ship je een van de onderstaande permanente fixes.

Permanente CSS-fix scoped op capture-modus

De schoonste oplossing als je de stylesheet beheert, is een heatmap-only override die elk overflow-container uitschakelt zodra Matomo's tracker is geladen. Één CSS-blok, één JS-regel, en je bent klaar.

css
/* Edit the selectors to match the wrappers on your site */
html.heatmap-mode .sidebar,
html.heatmap-mode main,
html.heatmap-mode .modal-shell {
  overflow: visible !important;
  overflow-y: visible !important;
  height: auto !important;
  max-height: none !important;
}
js
// Add to your site. Flips the class on whenever Matomo's tracker is on the page.
if (window._paq) document.documentElement.classList.add('heatmap-mode');

De override treedt nu alleen in werking op pagina's waar Matomo sowieso laadt, wat prima is voor een heatmap-review en onzichtbaar is voor de rest van je verkeer. Als je het nog specifieker wilt scopen, stel je het in op een query param (?heatmap=1) of een aparte stylesheet die alleen is opgenomen in de stagingkopie waarop je Matomo richt.

Verwijder de geneste scrollcontainer helemaal

Als je pagina overflow: auto heeft op <main> of een andere wrapper vanwege "scrollytelling"-redenen, is de architectonische fix om die te verwijderen en het document te laten scrollen. Matomo's renderer kan document-scroll reproduceren, een geneste scroll-positie kan hij niet. Je krijgt er ook een betere mobiele UX van (iOS Safari heeft zijn eigen mening over geneste scrollcontainers) en de toegankelijkheid verbetert doordat toetsenbord-scroll en schermlezersnavigatie gaan werken zoals gebruikers verwachten.

Het is een grotere wijziging dan de CSS-override, maar het is de oplossing die voorkomt dat het probleem terugkomt de volgende keer dat iemand een nieuw gedeelte aan de layout toevoegt.

Vervang clearfix overflow: hidden door display: flow-root

Oude clearfix-wrappers met overflow: hidden waren een workaround om gefloate children te bevatten. display: flow-root doet hetzelfde zonder iets af te knippen, en het is al veilig te gebruiken in elke browser die het waard is te ondersteunen — al sinds 2018.

css
/* Edit the selector to match your clearfix wrapper */
.row-with-floats {
  display: flow-root;
}

Hetzelfde containment-gedrag, geen clipping, en je stopt met vechten tegen de screenshot. De snelle manier om kandidaten te vinden is om je CSS te doorzoeken op overflow: hidden en te controleren of elk geval werkelijk iets bedoeld is te knippen. Meestal niet.

Een noot over de afweging

Welke permanente fix je ook kiest, scope de override (een class, een query param, een heatmap-only stylesheet) in plaats van de live stijlen voor iedereen aan te passen. Je bezoekers zien de layout die je voor hen hebt ontworpen, en de modal die bedoeld is om binnen zijn eigen box te scrollen, moet dat voor hen blijven doen. De override hoeft alleen te bestaan voor de renderer die Matomo op de pagina richt.

Waarom Matomo je overflow-containers niet kan renderen

Matomo's screenshot-capture is een proces in twee stappen. Eerst serialiseert de tracker je live DOM naar HTML en stuurt die weg. Daarna rendert je Matomo-server die HTML bij een vaste viewport om de screenshot te produceren die je in de heatmapweergave ziet. Geen browsersessie, geen scroll-positie overgenomen van je bezoeker, geen JS-gestuurde layout-aanpassingen. Gewoon een HTML-bestand dat koud wordt gerenderd vanaf een ander IP-adres.

De renderer kan document-scroll reproduceren — dat is waarom lange pagina's goed worden vastgelegd. Wat hij niet kan reproduceren, is de interne scroll-positie van een geneste overflow: auto-container, want die leeft in de browser van de bezoeker en komt nooit terecht in de geserialiseerde DOM. Wanneer de renderer een overflow: hidden-wrapper tegenkomt, doet hij wat elke browser doet en knipt. Het resultaat is een screenshot die eruitziet als de zichtbare uitsnede van de pagina op één specifiek moment, met alles wat daarna komt verdwenen.

De klikcoördinaten maken het erger. Matomo registreert klikken ten opzichte van het document, niet ten opzichte van de interne scroll-positie van de container die de gebruiker aan het scrollen was. Een klik op het derde item in een scrollbare sidebar wordt opgeslagen op de y-coördinaat die het op het moment van de klik innam. Wanneer de renderer de sidebar screenshot bij scroll-positie nul, behoort die y-coördinaat nu aan het eerste item toe. De markering wordt daar geplaatst. De klik lijkt op de verkeerde link te landen.

Wat er eigenlijk misgaat

Een paar patronen die we steeds weer tegenkomen:

  • Sidebars en modals met hun eigen scrollbalken. De wrapper heeft een vaste hoogte en overflow: auto, de binnenste lijst is langer, en alleen het deel dat zichtbaar was tijdens het vastleggen komt in de screenshot terecht.
  • "Scrollytelling"-pagina's met overflow: auto op <main> of een vergelijkbare wrapper. De body scrolt niet, het binnenste element wel, en de heatmap legt één viewport aan content vast.
  • Verouderde clearfix-wrappers met overflow: hidden die eigenlijk niets bedoeld zijn te knippen. Ze waren bedoeld om floats te bevatten. Ze knippen nog steeds, en ze laten content uit de screenshot vallen.
  • Aangepaste dropdown-menu's of accordions die animeren door hun content te knippen met overflow: hidden plus een hoogte-transitie. De gesloten toestand is wat Matomo ziet.
  • CSS mask of clip-path op een container. Andere eigenschap, zelfde effect op de screenshot. De fix is hetzelfde: zet hem uit in capture-modus.

Als je heatmap content mist of de klikmarkeringen niet overeenkomen met de elementen die je verwacht, loop je tegen minstens een van deze problemen aan. Vaak meer dan één op dezelfde pagina.

Dit is ook dezelfde familie van problemen die lettertypen, afbeeldingen en sticky headers in dezelfde screenshot kapotmaakt. We hebben een uitgebreider artikel over kapotte Matomo heatmap-screenshots dat de rest behandelt, en aparte artikelen over waarom lettertypen niet laden en waarom afbeeldingen niet laden — want die komen bijna even vaak voor.

Wat we zelf zouden doen

Als je de stylesheet beheert, ship de heatmap-only override. Één CSS-blok geschoped op een class die je inschakelt wanneer Matomo's tracker is geladen. Het is de kleinste diff die het probleem permanent oplost en die niet raakt aan wat bezoekers zien.

Als de layout een geneste scrollcontainer heeft die je ook om andere redenen al dwars zat, neem dan de architectonische fix en laat het document scrollen. Je bespaart jezelf dit probleem bij elke toekomstige heatmap-capture, en je mobiele gebruikers zullen je er dankbaar voor zijn.

Als geen van beide haalbaar is (geen toegang tot de stylesheet, third-party widgets die je niet kunt herstylen, een CMS dat geen aangepaste CSS toestaat), is de Matomo Heatmap Helper Chrome-extensie wat we gebruiken op clientsites waar we de stack niet kunnen wijzigen. Hij loopt langs dezelfde overflow-containers die de snippet vindt, neutraliseert ze voor de capture en herstelt ze daarna — zodat de pagina intact is voor de volgende bezoeker. Gratis, open source, code op GitHub.

Martez is het grotere project waar de extensie uit voort is gekomen. Het verbindt Matomo met Meta Ads en Google Ads zodat ROAS, CLV en attributie naast je webanalytics staan in plaats van in een apart spreadsheet. Het is in private beta. Meld je aan voor de wachtlijst als dat relevant voor je is.

Meestal is dit een stylesheet-wijziging verwijderd. De moeite waard om één keer te doen.

Waarom delen van je pagina worden afgesneden in je Matomo heatmap (en hoe je dat repareert) - Martez Blog