Warum dein Sticky-Header in der Matomo-Heatmap wiederholt wird (oder einfriert) – und wie du es behebst

Fixierte und klebende Header wiederholen sich in Matomo-Heatmap-Screenshots entweder über die ganze Seite oder frieren irgendwo in der Mitte ein, weil Matomo das erfasste DOM als ein einziges hohes Bild rendert – ohne konsistenten Viewport, auf den sie sich verankern könnten. Hier erfährst du, warum das passiert und wie wir damit umgehen.

Wenn du eine Matomo-Heatmap geöffnet hast und deinen Header wie eine Briefmarke zwei, drei oder manchmal sechs Mal die Seite hinuntergestempelt siehst – oder eine einzelne Kopie davon quer über die Mitte gefroren, die den Inhalt darunter verdeckt – dann sind die Klicks selbst in Ordnung. Matomo hat sie an den richtigen Koordinaten aufgezeichnet. Was kaputt ist, ist der Screenshot darunter. Dein Sticky-Header hat sich nicht so gerendert, wie er es im Browser tut, also wiederholt er sich entweder bei jeder Scrollposition oder pinnt sich an die Stelle, an der der Renderer zufällig hingeschaut hat. So oder so werden alle Klicks darunter auf Nav-Links geplottet, und ein Teil deiner echten Heatmap wird zu einer einzigen großen toten Zone.

Die schnellste Lösung ist eine kostenlose Chrome-Erweiterung, die wir pflegen: Matomo Heatmap Helper. Sie erkennt jeden fixierten und klebenden Header auf der Seite, tauscht ihn während der Erfassung gegen position: relative mit einem gleichhohen Platzhalter aus und stellt ihn danach wieder her. Der Rest dieses Posts erklärt, wie du dasselbe ohne Erweiterung hinbekommst – plus ein dauerhaftes CSS-Muster, das sich lohnt auszuliefern, wenn du regelmäßig Heatmaps erfasst.

So behebst du es ohne die Erweiterung

Es gibt ein Konsolensnippet, das das Problem kurz vor jeder Erfassung überdeckt, und einen dauerhaften CSS-Branch, den du mit der Seite ausliefern kannst. Nimm, was zu deinen Rahmenbedingungen passt.

Jeden fixierten und klebenden Header auf der Seite finden

Bevor du irgendetwas änderst, finde heraus, was wirklich verankert ist. Öffne die Seite in Chrome, drück F12 um DevTools zu öffnen, wechsle zur Konsole und füge Folgendes ein:

js
// Loggt jedes header-ähnliche Element und seinen berechneten position-Wert
document.querySelectorAll('header, nav, [role=banner], [role=navigation], .header, .navbar, .nav, .sticky')
  .forEach(el => console.log(getComputedStyle(el).position, el));

Alles, was fixed oder sticky meldet, ist verdächtig. Auf den meisten Marketing-Seiten findest du mehr als eines. Der Hauptheader ist fixed, die Cookie-Bar ist fixed, ein „nach oben"-Button ist fixed, eine sekundäre Sub-Navigation ist sticky. Sie alle stapeln sich und sie alle beschädigen den Screenshot auf ihre eigene Weise.

Jeden Header kurz vor der Erfassung lösen

Füge das hier ein, bevor du die Heatmap-Erfassung auslöst. Es spiegelt, was der sticky-header-fixer der Erweiterung macht: Jeden fixierten und klebenden Header auf position: relative umstellen und für fixed-Header (die aus dem Dokumentenfluss herausgelöst waren) einen unsichtbaren gleichhohen Platzhalter einfügen, damit Scrollposition und Klickkoordinaten nicht verrutschen.

js
// In die Browser-Konsole einfügen. Löst jeden fixed/sticky-Header, damit er
// sich nicht wiederholt oder über der Heatmap schwebt, und fügt einen Platzhalter
// gleicher Höhe/Breite ein, um den Rest des Layouts beizubehalten.
(() => {
  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.');
})();

Ausführen, die Anzahl prüfen, Erfassung auslösen. Wenn es Unstuck 0 headers loggt und du trotzdem einen sich wiederholenden Header auf der Seite siehst, fehlt in der Selektor-Liste oben die Klasse, die dein Header tatsächlich verwendet. Element inspizieren, das übergeordnete Element mit position: fixed finden und seinen Selektor zu SELECTOR hinzufügen.

Oder den Header während der Erfassung einfach ausblenden

Eine Heatmap auf einem Sticky-Header liefert ohnehin selten nützliche Erkenntnisse. Besucher scrollen, der Header bewegt sich mit ihnen, und jeder Klick auf die Navigation landet an einer anderen Seiten-Y-Koordinate. Das Ergebnis ist eine Heatmap der Navigation, die über die gesamte Seitenhöhe verschmiert ist. Wenn der Header nicht das ist, was du untersuchen willst, ist der einfachste Schritt, ihn komplett aus dem Bild zu nehmen:

js
// Blendet jeden Header während der Erfassung aus
document.querySelectorAll('header, nav, [role=banner], .navbar, .sticky')
  .forEach(el => el.style.display = 'none');

Die Erfassung kommt zurück, als wäre der Header gar nicht da. Echte Klicks darunter landen trotzdem auf den richtigen Elementen, weil sich die Klickkoordinaten nicht ändern. Du verlierst lediglich die (meist nutzlosen) Heatmap-Daten der Navigation selbst.

Dauerhafter CSS-Fix, auf den Erfassungsmodus beschränkt

Wenn du wiederholt Heatmaps auf denselben Seiten erfasst, nervt das Konsolen-Einfügen schnell. Besser ist ein CSS-Branch, der sich nur aktiviert, wenn du die URL mit einem Flag wie ?matomo_heatmap=1 aufrufst. Füge dem <html>-Element eine Klasse hinzu, wenn das Flag vorhanden ist:

js
// Zu deiner Seite (oder deinem Tag-Manager) hinzufügen
if (new URLSearchParams(location.search).has('matomo_heatmap')) {
  document.documentElement.classList.add('heatmap-mode');
}

Dann liefere CSS aus, das jeden Header innerhalb dieser Klasse löst:

css
/* Selektoren an die Header-Markup-Struktur deiner Seite anpassen */
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;
}

Der Erfassungsworkflow ist dann nur noch: ?matomo_heatmap=1 aufrufen, Heatmap auslösen, fertig. Kein Konsolen-Einfügen, kein Risiko des Vergessens. Der Nachteil: Der Platzhalter-Trick (das ursprüngliche Layout für position: fixed-Header erhalten) lässt sich in reinem CSS schwerer ausdrücken, also kann die Seite leicht springen, wenn du die Klasse umschaltest. Auf den meisten Seiten ist das kein Problem. Die Klickdaten sind das Entscheidende, und das Erscheinungsbild bleibt nah genug am echten Layout, um lesbar zu sein.

Warum Matomo Sticky-Header nicht korrekt rendern kann

Matomos Heatmap-Renderer nimmt das serialisierte DOM und rastert es als ein einziges hohes Bild – die gesamte Höhe der Seite vom Anfang des <body> bis ganz unten. In dieser Pipeline gibt es kein Scrollen. Es gibt keinen Viewport, so wie ein echter Browser einen Viewport hat. Es ist eine einzige große Canvas.

Ein Header mit position: fixed ist aus dem Dokumentenfluss herausgelöst und an den aktuellen Viewport gepinnt. Wenn der Renderer eine hohe Canvas ohne Viewport erzeugt, ist das Ergebnis undefiniert. Manche Renderer stempeln den Header bei jedem Scrollschritt, den sie gemacht hätten, also wiederholt er sich alle 600 bis 1.000 Pixel. Andere sperren ihn auf eine beliebige Y-Koordinate und lassen es dabei bewenden. position: sticky läuft aus der entgegengesetzten Richtung in dasselbe Problem: Die Sticky-Grenze ist relativ zu einem scrollenden Vorfahren definiert, und es gibt keinen scrollenden Vorfahren, wenn die Seite als ein Bild gerendert wird.

So oder so ist die visuelle Ausgabe falsch, und die Klicks (die vom In-Browser-Tracker gegen echte Seitenkoordinaten aufgezeichnet wurden) werden auf Header-Pixel geplottet, die nicht am richtigen Ort sind.

Was tatsächlich schiefläuft

Ein paar Muster, die uns immer wieder begegnen:

  • Der Hauptheader der Seite ist position: fixed, und der Renderer stempelt ihn alle 600 bis 1.000 Pixel die Seite hinunter. Das Hero, die Value-Props, die Testimonials – bei jedem davon schwebt dieselbe Navigation über dem Inhalt.
  • Eine Cookie-Consent-Bar ist position: fixed am unteren Viewport-Rand. Sie taucht in der Mitte des Screenshots auf und verdeckt die Preistabelle oder das Produktraster.
  • Ein „nach oben"-Button oder ein schwebendes Chat-Widget ist position: fixed unten rechts. Es erscheint über jeden Abschnitt gestempelt und wirft Klick-Marker auf Inhalte, die nichts mit dem Button zu tun haben.
  • Eine Sub-Navigation nutzt position: sticky mit hohem z-index und wird vom Renderer auf einer beliebigen Scrollposition eingefroren. Jeder Klick unterhalb dieser Y-Koordinate, irgendwo auf der Seite, wird auf die Sub-Navigation geplottet.
  • Ein Modal oder Banner, das der Besucher vor dem Klicken geschlossen hatte, taucht trotzdem im Screenshot auf, weil der Schließzustand in JavaScript lebte und der Renderer frisch vom serialisierten DOM startete.

Wenn deine Heatmap wiederholte Navigation, eingefrorene Overlays oder Klickkoordinaten auf Header-Pixeln zeigt, triffst du mindestens eines davon. Auf jeder Seite mit Hauptheader und Cookie-Bar triffst du gleich zwei.

Das ist auch dieselbe Art von Problem, die Schriftarten, Bilder und Scroll-Container im selben Screenshot beschädigt. Wir haben einen ausführlicheren Post über fehlerhafte Matomo-Heatmap-Screenshots geschrieben, der den Rest durchgeht, plus separate Posts über Schriftarten und Bilder, da die fast genauso häufig auftauchen.

Was wir tatsächlich tun würden

Wenn du Heatmaps auf denselben Seiten mehr als ein paarmal erfasst, liefere den ?matomo_heatmap=1-CSS-Branch aus. Es sind ein paar Zeilen CSS, du richtest es einmal ein, und der Workflow danach besteht nur noch aus einem Query-Parameter. Der leichte Layout-Sprung durch das Weglassen der Platzhalter ist fast immer in Ordnung.

Wenn du keinen Zugriff auf Templates oder CSS hast, füge das Unstick-Snippet vor jeder Erfassung ein. Nicht schön, aber es funktioniert auf jeder Seite, auf der du DevTools öffnen kannst.

Für Kundenprojekte, bei denen wir viele Seiten erfassen und keinen Code in den Stack von jemand anderem einbauen wollen, macht die Chrome-Erweiterung Matomo Heatmap Helper das automatisch. Sie erkennt jeden fixierten und klebenden Header, tauscht ihn gegen position: relative mit einem gleichhohen Platzhalter aus, führt die Erfassung durch und stellt danach alles wieder her. Dieselbe Logik deckt Schriftarten, Bilder und Scroll-Container in einem Schritt ab. 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 deinen Web-Analytics-Daten liegen – statt in einem separaten Spreadsheet. Es ist in der privaten Beta. Trag dich auf die Warteliste ein, wenn das für dich relevant ist.

Meistens ist das ein einziger CSS-Branch entfernt. Den Aufwand lohnt es.

Warum dein Sticky-Header in der Matomo-Heatmap wiederholt wird (oder einfriert) – und wie du es behebst - Martez Blog