Perché la heatmap di Matomo si interrompe a un certo punto (e come risolvere)

Gli screenshot delle heatmap di Matomo diventano neri o vuoti sotto una certa riga quando una sidebar, un pannello a tab o qualsiasi container con altezza fissa taglia il suo overflow durante la cattura. I clic vengono comunque registrati, ma atterrano su spazio vuoto. Ecco perché succede e come lo gestiamo.

Se hai aperto una heatmap di Matomo e hai trovato tutto sotto una certa riga sostituito da un rettangolo nero, la sidebar collassata all'altezza che aveva a schermo e i marker dei clic accatastati inutilmente su colore piatto, i clic in sé sono a posto. Matomo li ha registrati alle coordinate giuste. Quello che non funziona è lo screenshot sotto di loro. Qualche container sulla tua pagina live aveva l'altezza fissa e lasciava scorrere il contenuto al suo interno (la sidebar, il pannello a tab, lo slide-over con le FAQ) e non è riuscito a portare il contenuto completo nel DOM catturato. Il container ha mantenuto la sua altezza fissa durante la fase di screenshot, e Matomo ha dipinto l'area oltre di esso come spazio vuoto.

La soluzione più rapida è un'estensione gratuita per Chrome che manteniamo noi, che si chiama Matomo Heatmap Helper. Subito prima di ogni cattura, percorre tutti gli elementi della pagina, trova quelli il cui scrollHeight supera il clientHeight, e riscrive height e max-height in modo che lo screenshot veda il contenuto completo. Ripristina i valori originali quando la cattura è finita. Il resto di questo post spiega come fare la stessa cosa senza estensione, più un pattern CSS permanente che vale la pena mettere in produzione.

Come risolvere senza l'estensione

C'è uno snippet da console che copre il problema subito prima di ogni cattura, e un pattern CSS attivabile tramite query param che puoi mettere in produzione mantenendo intatta l'UX degli utenti reali. Scegli quello che si adatta meglio al tuo stack.

Trovare gli elementi responsabili

Prima di cambiare qualcosa, trova i container che stanno tagliando il contenuto. Apri la pagina in Chrome, premi F12 per aprire DevTools, passa alla Console, incolla questo snippet, premi Invio. Qualsiasi elemento con una differenza superiore a cinque pixel circa è un candidato:

js
// Incolla nella console del browser sulla pagina interessata.
// Elenca ogni elemento il cui contenuto è tagliato da un'altezza fissa.
Array.from(document.querySelectorAll('*'))
  .filter(el => el.scrollHeight > el.clientHeight + 1)
  .forEach(el => console.log(el.scrollHeight - el.clientHeight, el));

Le piccole differenze (uno o due pixel) di solito sono arrotondamenti subpixel e non sono il tuo problema. Qualsiasi elemento che riporta centinaia o migliaia di pixel di contenuto tagliato è quello che cerchi. È lì che la heatmap diventa nera.

La soluzione rapida: espandere forzatamente ogni container tagliato

Questo è lo snippet che incolliamo nella console del browser subito prima di attivare la cattura della heatmap di Matomo. Percorre ogni elemento della pagina, espande quelli il cui contenuto va in overflow, e disattiva le regole overflow in modo che il contenuto completo sia visibile. Quando Matomo serializza il DOM, le sezioni tagliate sono già aperte.

js
// Incolla nella console. Espande ogni container con overflow nascosto.
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';
  }
});

Per confermare che non ci sia ancora nulla di tagliato prima di avviare la cattura:

js
// Restituisce il numero di elementi che tagliano ancora il contenuto. Deve essere zero.
Array.from(document.querySelectorAll('*'))
  .filter(el => el.scrollHeight > el.clientHeight + 1).length;

Se è zero, sei a posto. Se non lo è, gli elementi rimanenti di solito si trovano dentro shadow root o iframe cross-origin che lo snippet non riesce a raggiungere. Ne parliamo più avanti.

Questa è la via rapida. Funziona per catture una tantum e per i cicli di revisione in cui hai bisogno di uno screenshot pulito adesso e non vuoi aspettare una modifica al codice. Per le pagine che ricatturi regolarmente, metti in produzione la soluzione permanente qui sotto.

La soluzione permanente: un foglio di stile solo per la heatmap

Il trucco è mantenere intatta l'UX degli utenti reali (sidebar sticky, pannelli a tab con scroll, tutto quello che esiste per un motivo) permettendo al passaggio di cattura della heatmap di vedere il contenuto completo. Una classe CSS attivabile tramite query param è il modo più semplice per farlo:

css
/* Modifica i selettori per adattarli ai container del tuo sito */
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
// Aggiungi al tuo sito. Attiva l'override quando visiti con ?heatmap=1
if (new URLSearchParams(location.search).get('heatmap') === '1') {
  document.documentElement.classList.add('heatmap-capture');
}

Ora visitare https://your-site.com/page?heatmap=1 mette la pagina in modalità cattura. Gli utenti reali accedono alla pagina senza il param e mantengono la normale UX di scroll. Stesso sito, due layout, un query param a separarli.

Se non vuoi esporre il trigger nell'URL, sostituisci il controllo del param con un cookie impostato da una route solo admin, oppure con un controllo sullo User-Agent del fetcher di screenshot di Matomo (cerca nei log di accesso l'esatta stringa UA che usa il renderer di Matomo, poi fai il gate server-side). Stessa idea, porta diversa.

Il compromesso

Non mettere in produzione max-height: none per tutti i visitatori. Le sidebar sticky, i pannelli a tab con scroll e i drawer con altezza fissa esistono per un motivo: tengono la navigazione in vista, permettono ai pannelli di coesistere con il resto del layout, impediscono a un accordion di 4.000 pixel di prendere il controllo della pagina. L'intero punto di fare il gate sull'override è mantenere la produzione come gli utenti reali se la aspettano e cambiare il layout solo durante la cattura.

Workflow testato: apri la pagina in una finestra in incognito con ?heatmap=1, clicca in giro per registrare la sessione heatmap, lascia la scheda aperta finché la cattura di Matomo si attiva. Gli utenti reali sulla stessa pagina nelle loro sessioni normali non vedono mai l'override.

Perché Matomo non riesce a catturare oltre il punto di interruzione

La cattura degli screenshot di Matomo è un processo in due fasi. Prima, il tracker serializza il DOM live in HTML e lo spedisce via. Poi, il tuo server Matomo ri-renderizza quell'HTML per produrre lo screenshot che vedi nella vista heatmap. Il renderer dipinge la pagina esattamente come la descrive il DOM serializzato. Se un container in quel DOM ha height: 600px e overflow: hidden con contenuto che è in realtà alto 2.000 pixel, solo i 600 pixel superiori finiscono nell'immagine. Tutto il resto viene tagliato al bordo del container, e il canvas della heatmap dipinge l'area oltre di esso come spazio vuoto (nero, bianco o trasparente a seconda di cosa c'è sotto).

Le coordinate dei clic vengono tracciate a livello di documento, quindi sopravvivono al taglio senza problemi. I pixel a cui puntavano no.

Cosa sta andando storto

Alcuni pattern che continuiamo a incontrare:

  • Sidebar con max-height: calc(100vh - header) e overflow-y: auto, comuni per mega-nav, filtri faccettati e pannelli chat. La sidebar è più alta della viewport sulla pagina live. Lo screenshot vede solo la fetta alta quanto la viewport.
  • Pannelli a tab dove ogni [role=tabpanel] ha il proprio container di scroll, comportamento predefinito in Bootstrap, Tailwind UI, MUI e la maggior parte delle librerie di componenti. Il tab attivo viene tagliato alla sua altezza fissa e i tab inattivi non sono visibili per niente.
  • Sezioni a drawer che non sono veri modal: slide-over, pannelli di filtri espansi, pannelli carrello inline. Qualsiasi cosa fissata a 100vh o a un'altezza in pixel fissa di proposito. Il container rimane fisso durante la cattura.
  • Card dashboard con scroller interni, tile ad altezza fissa dove il contenuto va in overflow dentro un div overflow-y: auto. Matomo fotografa la tile alle sue dimensioni esterne e tutto ciò che supera la fetta visibile dello scroller interno è perso.
  • Librerie di componenti che lo fanno di default: ScrollArea di shadcn/ui, il Drawer di MUI con scroll interno, le liste combobox di Tailwind UI, i pannelli tab di Headless UI. Vale la pena saperlo quando si fa l'audit della pagina — l'elemento responsabile è spesso tre livelli dentro un wrapper che non hai scritto tu.
  • Iframe con dimensioni fisse. Il DOM dell'iframe viene tagliato alle dimensioni esterne dell'iframe, e Matomo non può comunque accedere agli iframe cross-origin.

Se la tua heatmap diventa nera oltre una certa riga, stai incontrando almeno uno di questi. La maggior parte delle pagine con una sidebar contenuto ne incontra due contemporaneamente.

Questa è anche la stessa famiglia di problemi che rompe font, immagini e header sticky nello stesso screenshot. Abbiamo scritto un post più lungo sugli screenshot rotti delle heatmap di Matomo che copre il resto, più post separati su perché i font non si caricano nella tua heatmap di Matomo e perché le immagini non si caricano nella tua heatmap di Matomo.

Cosa faremmo noi

Se controlli i template, metti in produzione il foglio di stile con gate. Sono una manciata di selettori e un controllo su un query param, e rimangono in produzione per tutto il tempo in cui usi le heatmap di Matomo. Cinque minuti di lavoro, dura per sempre.

Se non puoi, lo snippet da console copre lo stesso terreno per catture una tantum. Il limite è che devi ricordarti di incollarlo prima di ogni cattura.

Se nessuna delle due opzioni è raggiungibile (template bloccati da CMS, widget di terze parti che non controlli, qualsiasi situazione in cui non puoi aggiungere una regola CSS o un tag script), l'estensione Chrome Matomo Heatmap Helper è quello che usiamo sui siti dei clienti dove non possiamo cambiare lo stack. Esegue la stessa riscrittura scrollHeight/maxHeight durante la cattura e ripristina i valori originali dopo, più correzioni equivalenti per font, immagini e header sticky. Gratuita, open source, codice su GitHub.

Martez è il progetto più ampio da cui è nata l'estensione. Collega Matomo con Meta Ads e Google Ads in modo che ROAS, CLV e attribution siano accanto alla tua web analytics invece che in un foglio di calcolo separato. È in beta privata. Unisciti alla waitlist se ti interessa.

Qualche selettore e un query param. Vale la pena farlo una volta.

Perché la heatmap di Matomo si interrompe a un certo punto (e come risolvere) - Martez Blog