Wenn du eine Matomo-Heatmap geöffnet hast und alles unterhalb einer bestimmten Linie durch ein schwarzes Rechteck ersetzt war, deine Sidebar auf der Höhe kollabiert ist, die sie auf dem Bildschirm hatte, und die Klick-Marker nutzlos auf einfarbigem Hintergrund stapeln – die Klicks selbst sind in Ordnung. Matomo hat sie an den richtigen Koordinaten aufgezeichnet. Was kaputt ist, ist der Screenshot darunter. Irgendein Container auf deiner Live-Seite hat seine eigene Höhe begrenzt und seinen Inhalt in einem scrollbaren Bereich fließen lassen (die Sidebar, das Tab-Panel, der ausklappbare FAQ-Bereich) – und dieser Container hat seinen vollständigen Inhalt beim Erfassungsvorgang nicht ins DOM bekommen. Er hat seine begrenzte Höhe während des Screenshot-Passes beibehalten, und Matomo hat den Bereich dahinter als leeren Raum dargestellt.
Der schnellste Fix ist eine kostenlose Chrome-Erweiterung, die wir pflegen: Matomo Heatmap Helper. Direkt vor jeder Erfassung geht sie jedes Element auf der Seite durch, findet die Elemente, deren scrollHeight ihre clientHeight übersteigt, und schreibt deren height und max-height so um, dass der Screenshot den vollständigen Inhalt sieht. Danach stellt sie die Originalwerte wieder her. Der Rest dieses Posts erklärt, wie du dasselbe ohne Erweiterung machst – plus ein dauerhaftes CSS-Muster, das sich lohnt zu shippen.
Wie du es ohne die Erweiterung behebst
Es gibt ein Konsolen-Snippet, das das Problem direkt vor jeder Erfassung überdeckt, und ein query-parameter-gesteuertes CSS-Muster, das du shippen kannst, um die UX deiner echten Nutzer zu erhalten. Nimm das, was zu deinem Stack passt.
Die betroffenen Elemente finden
Bevor du irgendetwas änderst, such die Container, die abschneiden. Öffne die Seite in Chrome, drücke F12 um die DevTools zu öffnen, wechsle zur Konsole, füge das hier ein und drücke Enter. Alles, was mit einem Unterschied von mehr als fünf Pixeln geloggt wird, ist ein Kandidat:
// Paste into the browser console on the affected page.
// Lists every element whose contents are clipped by a capped height.
Array.from(document.querySelectorAll('*'))
.filter(el => el.scrollHeight > el.clientHeight + 1)
.forEach(el => console.log(el.scrollHeight - el.clientHeight, el));Winzige Unterschiede (ein oder zwei Pixel) sind meistens Subpixel-Rundung und nicht dein Problem. Alles, was hunderte oder tausende Pixel abgeschnittenen Inhalt meldet, ist das, wonach du suchst. Genau da wird die Heatmap dunkel.
Der schnelle Fix: alle abgeschnittenen Container aufklappen
Das ist das Snippet, das wir in die Browserkonsole einfügen, direkt bevor wir Matomos Heatmap-Erfassung auslösen. Es geht jedes Element auf der Seite durch, klappt die Elemente auf, deren Inhalt überläuft, und schaltet ihre Overflow-Regeln aus, sodass der vollständige Inhalt sichtbar ist. Bis Matomo das DOM serialisiert, sind die abgeschnittenen Bereiche aufgerollt.
// Paste into the console. Expands every container that has hidden overflow.
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';
}
});Um zu bestätigen, dass nichts mehr abgeschnitten wird, bevor du die Erfassung auslöst:
// Returns the number of elements still clipping their contents. Should be zero.
Array.from(document.querySelectorAll('*'))
.filter(el => el.scrollHeight > el.clientHeight + 1).length;Wenn das null ist, bist du gut. Wenn nicht, befinden sich die verbleibenden Elemente meistens in Shadow Roots oder Cross-Origin-Iframes, die das Snippet nicht erreichen kann. Dazu gleich mehr.
Das ist der schnelle Weg. Er funktioniert für einmalige Erfassungen und die Review-Zyklen, bei denen du jetzt einen sauberen Screenshot brauchst und nicht auf eine Code-Änderung warten willst. Für Seiten, die du regelmäßig neu erfasst, ship den dauerhaften Fix unten.
Der dauerhafte Fix: ein heatmap-only-Stylesheet
Der Trick ist, die UX deiner echten Nutzer intakt zu lassen (klebende Sidebars, scrollbare Tab-Panels, all das Zeug, das aus gutem Grund existiert), während der Heatmap-Pass den vollständigen Inhalt sieht. Eine query-parameter-gesteuerte CSS-Klasse ist der einfachste Weg dafür:
/* Edit the selectors below to match your site's containers */
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;
}// Add to your site. Turns the override on when you visit with ?heatmap=1
if (new URLSearchParams(location.search).get('heatmap') === '1') {
document.documentElement.classList.add('heatmap-capture');
}Jetzt versetzt https://your-site.com/page?heatmap=1 die Seite in den erfassungsfreundlichen Modus. Echte Nutzer rufen die Seite ohne den Parameter auf und behalten ihre normale Scroll-UX. Dieselbe Website, zwei Layouts, ein Query-Parameter dazwischen.
Wenn du den Trigger nicht in der URL haben willst, tausch die Parameter-Prüfung gegen ein Cookie aus, das über eine Admin-Route gesetzt wird, oder gegen eine User-Agent-Prüfung gegen Matomos Screenshot-Fetcher (grep deine Access-Logs nach dem genauen UA-String, den Matomos Renderer verwendet, und gate dann serverseitig darauf). Gleiche Idee, andere Absicherung.
Der Kompromiss
Schick max-height: none nicht an jeden Besucher. Klebende Sidebars, scrollbare Tab-Panels und höhenbegrenzte Drawer existieren aus gutem Grund: Sie halten die Navigation im Blick, sie lassen Panels neben dem restlichen Layout koexistieren, sie verhindern, dass ein 4.000 Pixel hohes Akkordeon die Seite übernimmt. Der ganze Sinn der Absicherung des Overrides ist es, die Produktion so zu lassen, wie echte Nutzer sie erwarten, und das Layout nur umzuschalten, wenn du erfasst.
Bewährter Workflow: Öffne die Seite in einem Inkognito-Fenster mit ?heatmap=1, klick herum um die Heatmap-Session aufzuzeichnen, lass den Tab offen bis Matomos Erfassung feuert. Echte Nutzer auf derselben Seite in ihren normalen Sessions sehen den Override nie.
Warum Matomo nicht über den Abschneidepunkt hinaus erfassen kann
Matomos Screenshot-Erfassung ist ein zweistufiger Prozess. Zuerst serialisiert der Tracker dein Live-DOM in HTML und schickt es ab. Dann rendert dein Matomo-Server dieses HTML, um den Screenshot zu erzeugen, den du in der Heatmap-Ansicht siehst. Der Renderer malt die Seite genau so, wie das serialisierte DOM sie beschreibt. Wenn ein Container in diesem DOM height: 600px und overflow: hidden hat, dessen Inhalt aber eigentlich 2.000 Pixel hoch ist, schaffen es nur die oberen 600 Pixel ins Bild. Alles darunter wird an der Container-Grenze abgeschnitten, und der Heatmap-Canvas malt den Bereich dahinter als leeren Raum (schwarz, weiß oder transparent, je nachdem was darunter liegt).
Die Klick-Koordinaten werden auf Dokumentebene getrackt, also überstehen sie das Abschneiden problemlos. Die Pixel, auf die sie zeigten, nicht.
Was tatsächlich schiefläuft
Ein paar Muster, auf die wir immer wieder stoßen:
- Sidebars mit
max-height: calc(100vh - header)undoverflow-y: auto, üblich für Mega-Navs, Facettenfilter und Chat-Panels. Die Sidebar ist auf der Live-Seite höher als der Viewport. Der Screenshot sieht nur den viewport-hohen Ausschnitt. - Tab-Panels, bei denen jedes
[role=tabpanel]seinen eigenen Scroll-Container hat – Standardverhalten in Bootstrap, Tailwind UI, MUI und den meisten Komponentenbibliotheken. Der aktive Tab wird an seiner begrenzten Höhe abgeschnitten, die inaktiven Tabs sind überhaupt nicht sichtbar. - Drawer-ähnliche Bereiche, die keine echten Modals sind: Slide-Overs, ausgeklappte Filter-Trays, eingebettete Warenkorb-Panels. Alles, was absichtlich auf
100vhoder eine feste Pixelhöhe begrenzt ist. Der Container bleibt während der Erfassung begrenzt. - Dashboard-Karten mit inneren Scrollern, Kacheln mit fester Höhe, bei denen der Inhalt in einem
overflow-y: auto-Div überläuft. Matomo macht einen Screenshot der Kachel in ihren äußeren Abmessungen, und alles oberhalb des sichtbaren Ausschnitts des inneren Scrollers ist weg. - Komponentenbibliotheken, die das eingebaut haben:
ScrollAreaaus shadcn/ui, MUIsDrawermit internem Scroll, Tailwind UIs Combobox-Listen, Headless-UI-Tab-Panels. Gut zu wissen, wenn du die Seite auditierst – das betroffene Element steckt oft drei Ebenen tief in einem Wrapper, den du nicht geschrieben hast. - Iframes mit begrenzten Abmessungen. Das eigene DOM des Iframes wird an der äußeren Iframe-Größe abgeschnitten, und Matomo kann ohnehin nicht in Cross-Origin-Iframes hineinschauen.
Wenn deine Heatmap ab einer bestimmten Zeile dunkel wird, triffst du mindestens eines davon. Die meisten Seiten mit einer Inhalts-Sidebar treffen zwei davon gleichzeitig.
Das ist auch dieselbe Familie von Problemen, die Schriftarten, Bilder und klebende Header im selben Screenshot kaputtmacht. Wir haben einen längeren Post über fehlerhafte Matomo-Heatmap-Screenshots geschrieben, der den Rest durchgeht, plus separate Posts darüber, warum Schriftarten in deiner Matomo-Heatmap nicht laden und warum Bilder in deiner Matomo-Heatmap nicht laden.
Was wir tatsächlich tun würden
Wenn du die Templates kontrollierst, ship das gesteuerte Stylesheet. Es sind ein paar Selektoren und eine Query-Parameter-Prüfung, und es bleibt in der Produktion, solange du Matomo-Heatmaps verwendest. Fünf Minuten Arbeit, hält ewig.
Wenn das nicht geht, deckt das Konsolen-Snippet denselben Bereich für einmalige Erfassungen ab. Der Haken ist, dass du daran denken musst, es vor jeder Erfassung einzufügen.
Wenn keines von beidem erreichbar ist (CMS-gesperrte Templates, Drittanbieter-Widgets, die dir nicht gehören, alles wo du keine CSS-Regel oder kein Script-Tag hinzufügen kannst), ist die Chrome-Erweiterung Matomo Heatmap Helper das, was wir auf Client-Sites einsetzen, wo wir den Stack nicht anfassen können. Sie führt dieselbe scrollHeight/maxHeight-Umschreibung während der Erfassung durch und stellt danach die Originalwerte wieder her – plus entsprechende Fixes für Schriftarten, Bilder und klebende Header. Kostenlos, Open Source, Code auf GitHub.
Martez ist das größere Projekt, aus dem die Erweiterung entstanden ist. Es verbindet Matomo mit Meta Ads und Google Ads, sodass ROAS, CLV und Attribution neben deiner Web-Analytics sitzen statt in einer separaten Tabelle. Es ist in der Private Beta. Trag dich auf die Warteliste ein, wenn das für dich relevant ist.
Ein paar Selektoren und ein Query-Parameter. Einmal machen, fertig.