Manuell skärmdump för Heatmap i Matomo
Problemet
Använd denna metod om Matomo inte automatiskt lyckas ta en skärmdump av din sida. Detta är vanligt för sidor som kräver inloggning, ligger bakom brandväggar eller använder avancerade JavaScript-ramverk.
Ibland kan Matomo Heatmap-pluginet inte automatiskt ta en skärmdump (HTML-snapshot) av webbsidan. Det kan bero på:
- Webbsidan kräver inloggning
- JavaScript-blockerare eller säkerhetsinställningar
- Komplexa single-page-applikationer (SPA)
- CORS-restriktioner
- Timing-problem vid sidladdning
När detta händer visas heatmapen utan bakgrundsbild och du ser meddelandet "No screenshot recorded yet". Eller lyckas Matomo att ta en skärmdump, men när vi tittar på den i Matomo saknar den element eller är trasig.
Lösningen
Vi har skapat en snippet som låter dig manuellt ta en skärmdump direkt från webbläsaren. Verktyget körs i webbläsarens konsol och skickar HTML-strukturen till Matomo.
Steg-för-steg-instruktioner
Steg 1: Hitta Heatmaps-ID (idSiteHsr)
- Logga in i Matomo
- Gå till Heatmaps → Hantera
- Klicka på den heatmap du vill ta en skärmdump för
- Titta på webbadressen i webbläsaren - leta efter
idSiteHsr=XXX - Anteckna detta nummer (t.ex.
idSiteHsr=47betyder att ID:t är 47)
Du kan också se ID:t direkt i listan över heatmaps om du håller muspekaren över länken "Visa heatmap", eller genom att titta i kolumnen ID om den är synlig.
Steg 2: Kontrollera att heatmappen är aktiv
Skärmdumpen kan endast sparas om heatmapen har status Aktiv. Kontrollera detta under Heatmaps → Hantera.
Om heatmappen redan har en skärmdump och du vill ta en ny:
- Gå till heatmap-vyn
- Scrolla ner och klicka på "Ta bort skärmdump"
- Bekräfta borttagningen
Steg 3: Navigera till webbsidan
- Öppna en ny flik i webbläsaren
- Gå till den webbsida som heatmapen ska visa
- Vänta tills sidan är fullständigt laddad
- Om sidan kräver inloggning loggar du in först
- Scrolla inte utan verktyget tar en skärmdump av sidan som den ser ut vid laddning
Steg 4: Öppna webbläsarens konsol
I Chrome/Edge:
- Tryck
F12eller högerklicka → "Inspektera" - Klicka på fliken "Console" (Konsol)
I Firefox:
- Tryck
F12eller högerklicka → "Inspektera element" - Klicka på fliken "Konsol"
I Safari:
- Aktivera först utvecklarverktyg: Safari → Inställningar → Avancerat → "Visa Utveckla-menyn"
- Tryck
Cmd+Option+Celler Utveckla → Visa JavaScript-konsol
Steg 5: Konfigurera och kör skriptet
- Kopiera hela skriptet nedan
- Klistra in det i konsolen
Innan du trycker Enter måste du ändra konfigurationen i början av skriptet så att det matchar din installation. Om du inte gör detta kommer skärmdumpen inte att sparas.
- Ändra dessa tre värden i början av skriptet:
const CONFIG = {
matomoUrl: "https://er-matomo-installation.se/", // Ändra till er Matomo-URL
idSite: 1, // Ändra till ert Site ID
idSiteHsr: 123, // Ändra till ert Heatmap-ID
};
- Tryck
Enterför att köra skriptet
Steg 6: Verifiera resultatet
Efter att skriptet körts ser du meddelanden i konsolen:
Vid lyckad körning:
[Matomo Snapshot] Starting DOM capture...
[Matomo Snapshot] Serializing DOM...
[Matomo Snapshot] DOM serialized: XXXXX bytes
[Matomo Snapshot] Uploading to https://...
[Matomo Snapshot] ✓ Request sent successfully!
Kontrollera i Matomo:
- Gå tillbaka till Matomo
- Öppna din heatmap
- Ladda om sidan (F5)
- Skärmdumpen bör nu visas
Skriptet
Kopiera allt nedan (från (function() till sista })();):
(function () {
"use strict";
// ============================================================================
// KONFIGURATION - Ändra dessa värden innan du kör skriptet
// ============================================================================
const CONFIG = {
matomoUrl: "https://er-matomo-installation.se/", // Er Matomo-URL (med avslutande /)
idSite: 1, // Ert Matomo Site ID
idSiteHsr: 123, // Heatmap-ID (idsitehsr)
};
// ============================================================================
console.log("[Matomo Snapshot] Starting DOM capture...");
class TreeMirrorClient {
constructor(target) {
this.target = target;
this.nextId = 1;
this.knownNodes = new Map();
}
capture() {
const rootId = this.serializeNode(this.target).id;
const children = [];
for (
let child = this.target.firstChild;
child;
child = child.nextSibling
) {
children.push(this.serializeNode(child, true));
}
return { rootId, children };
}
rememberNode(node) {
const id = this.nextId++;
this.knownNodes.set(node, id);
return id;
}
shouldMask(node) {
let current = node;
while (current && current.nodeType === Node.ELEMENT_NODE) {
if (
current.hasAttribute &&
(current.hasAttribute("data-matomo-mask") ||
current.hasAttribute("data-piwik-mask"))
) {
return true;
}
current = current.parentNode;
}
return false;
}
shouldMaskField(element) {
if (!element || !element.tagName) return false;
const tagName = element.tagName.toLowerCase();
if (
tagName !== "input" &&
tagName !== "textarea" &&
tagName !== "select"
) {
return false;
}
const type = (element.getAttribute("type") || "").toLowerCase();
if (["password", "email", "tel", "hidden"].includes(type)) {
return true;
}
if (this.shouldMask(element)) {
return true;
}
return false;
}
maskText(text) {
if (!text) return text;
return text.replace(/[^\s]/g, "*");
}
isEmptyTextNode(node) {
if (node.nodeType !== Node.TEXT_NODE) return false;
return !node.textContent || /^\s*$/.test(node.textContent);
}
isSvgElement(node) {
if (node.nodeType !== Node.ELEMENT_NODE) return false;
const tagLower = node.tagName.toLowerCase();
if (tagLower === "svg") return true;
let parent = node.parentNode;
while (parent && parent.nodeType === Node.ELEMENT_NODE) {
if (parent.tagName.toLowerCase() === "svg") return true;
parent = parent.parentNode;
}
return false;
}
serializeNode(node, recursive) {
if (node === null) return null;
if (this.isEmptyTextNode(node)) return null;
if (node.nodeType === Node.ELEMENT_NODE && this.isSvgElement(node))
return null;
if (
node.nodeType === Node.ELEMENT_NODE &&
node.tagName.toLowerCase() === "meta"
)
return null;
const existingId = this.knownNodes.get(node);
if (existingId !== undefined) {
return { id: existingId };
}
const data = {
nodeType: node.nodeType,
id: this.rememberNode(node),
};
switch (data.nodeType) {
case Node.DOCUMENT_TYPE_NODE:
data.name = node.name;
data.publicId = node.publicId;
data.systemId = node.systemId;
break;
case Node.COMMENT_NODE:
case Node.TEXT_NODE:
if (node.parentNode && this.shouldMask(node.parentNode)) {
data.textContent = this.maskText(node.textContent);
} else {
data.textContent = node.textContent;
}
break;
case Node.ELEMENT_NODE:
data.tagName = node.tagName;
data.attributes = {};
for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
let value = attr.value;
const attrNameLower = attr.name.toLowerCase();
if (attrNameLower.startsWith("on")) continue;
if (attrNameLower.startsWith("data-")) continue;
if (attrNameLower === "value" && this.shouldMaskField(node)) {
value = this.maskText(value);
}
if (
(attrNameLower === "href" || attrNameLower === "src") &&
value
) {
if (
value.toLowerCase().startsWith("javascript:") ||
value.toLowerCase().startsWith("data:")
) {
if (attrNameLower === "href") {
value = "#";
} else {
continue;
}
} else {
try {
value = new URL(value, window.location.href).href;
} catch (e) {}
}
}
data.attributes[attr.name] = value;
}
if (recursive && node.childNodes.length) {
data.childNodes = [];
for (
let child = node.firstChild;
child;
child = child.nextSibling
) {
const serialized = this.serializeNode(child, true);
if (serialized !== null) {
data.childNodes.push(serialized);
}
}
if (data.childNodes.length === 0) {
delete data.childNodes;
}
}
break;
}
return data;
}
}
function generateUniqueId() {
let result = "";
const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 6; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
function getWindowSize() {
return {
width:
window.innerWidth ||
document.documentElement.clientWidth ||
document.body.clientWidth,
height:
window.innerHeight ||
document.documentElement.clientHeight ||
document.body.clientHeight,
};
}
function getDocumentHeight() {
return Math.max(
document.body ? document.body.offsetHeight : 0,
document.body ? document.body.scrollHeight : 0,
document.documentElement ? document.documentElement.offsetHeight : 0,
document.documentElement ? document.documentElement.clientHeight : 0,
document.documentElement ? document.documentElement.scrollHeight : 0,
);
}
if (
!CONFIG.matomoUrl ||
CONFIG.matomoUrl === "https://er-matomo-installation.se/"
) {
console.error(
"[Matomo Snapshot] FEL: Ange CONFIG.matomoUrl (er Matomo-URL)",
);
return;
}
if (!CONFIG.idSite || CONFIG.idSite <= 0) {
console.error("[Matomo Snapshot] FEL: Ange CONFIG.idSite (Site ID)");
return;
}
if (!CONFIG.idSiteHsr || CONFIG.idSiteHsr <= 0) {
console.error("[Matomo Snapshot] FEL: Ange CONFIG.idSiteHsr (Heatmap-ID)");
return;
}
let matomoUrl = CONFIG.matomoUrl;
if (!matomoUrl.endsWith("/")) {
matomoUrl += "/";
}
console.log("[Matomo Snapshot] Serializing DOM...");
const mirror = new TreeMirrorClient(document);
const domData = mirror.capture();
const domJson = JSON.stringify(domData);
console.log(`[Matomo Snapshot] DOM serialized: ${domJson.length} bytes`);
const hsrVid = generateUniqueId();
const windowSize = getWindowSize();
const docHeight = getDocumentHeight();
const currentUrl = window.location.href;
const events = [
{
ty: 8,
dom: domJson,
id: CONFIG.idSiteHsr,
ti: 0,
},
];
const formData = new URLSearchParams();
formData.append("idsite", CONFIG.idSite);
formData.append("rec", "1");
formData.append("url", currentUrl);
formData.append("hsr_vid", hsrVid);
formData.append("hsr_ids[]", CONFIG.idSiteHsr);
formData.append("hsr_vw", windowSize.width);
formData.append("hsr_vh", windowSize.height);
formData.append("hsr_ti", "0");
formData.append("hsr_smp", "0");
formData.append(
"hsr_fyp",
Math.round((windowSize.height / docHeight) * 1000),
);
formData.append("ca", "1");
events.forEach((event, index) => {
Object.keys(event).forEach((key) => {
formData.append(`hsr_ev[${index}][${key}]`, event[key]);
});
});
const trackerUrl = matomoUrl + "matomo.php";
console.log(`[Matomo Snapshot] Uploading to ${trackerUrl}...`);
fetch(trackerUrl, {
method: "POST",
body: formData,
mode: "no-cors",
credentials: "omit",
})
.then((response) => {
console.log("[Matomo Snapshot] ✓ Request sent successfully!");
console.log(
"[Matomo Snapshot] Kontrollera din heatmap i Matomo för att verifiera att skärmdumpen sparades.",
);
console.log("[Matomo Snapshot] Om det inte fungerade, kontrollera att:");
console.log(" 1. Heatmappen är AKTIV (inte pausad eller avslutad)");
console.log(" 2. idSiteHsr är korrekt");
console.log(
" 3. Heatmappen inte redan har en skärmdump (ta bort den först i Matomo)",
);
})
.catch((error) => {
console.error("[Matomo Snapshot] ✗ Fel vid uppladdning:", error);
});
})();
Felsökning
"FEL: Ange CONFIG.matomoUrl"
Du har inte ändrat konfigurationen. Redigera värdena i CONFIG-sektionen innan du kör skriptet.
Skärmdumpen visas inte i Matomo
- Kontrollera att heatmapen är aktiv - Pausade eller avslutade heatmaps kan inte ta emot nya skärmdumpar
- Ta bort befintlig skärmdump - Om det redan finns en skärmdump måste du först ta bort den
- Vänta några sekunder och ladda om heatmap-sidan i Matomo
- Kontrollera rätt ID - Dubbelkolla att idSiteHsr matchar din heatmap
"DOM serialized: 0 bytes" eller mycket liten storlek
Sidan kanske inte har laddats fullständigt. Vänta tills sidan är helt laddad och försök igen.
Om du ser nätverksfel (röda felmeddelanden i konsolen) beror det ofta på CORS-inställningar (Cross-Origin Resource Sharing). Detta är en säkerhetsfunktion i webbläsaren.
CORS-fel eller nätverksfel
Om du ser nätverksfel kan det bero på säkerhetsinställningar. Kontakta i första hand din it-avdelning för hjälp, i andra hand Whitespace support. Det finns plugin som du kan installera till din webbläsare som temporärt deaktiverar CORS-kontroller, till exempel Allow CORS.
Teknisk information
Skriptet gör följande:
- Serialiserar DOM - Konverterar sidans HTML-struktur till ett JSON-format som Matomo förstår
- Filtrerar bort känslig data:
- Lösenordsfält maskeras automatiskt
- E-post- och telefonfält maskeras
- Element markerade med
data-matomo-maskmaskeras
- Optimerar storleken genom att exkludera:
- Meta-taggar
- Data-attribut
- Inline SVG-grafik
- Tomma textnoder
- Skickar till Matomo via tracking-API:et
Support
Om du har problem med verktyget eller behöver hjälp, kontakta Whitespace support med följande information:
- Heatmap-ID (idSiteHsr)
- Felmeddelanden från konsolen (om några)
- Webbläsare och version
- URL till sidan du försöker ta skärmdump av (om möjligt)