Se você já abriu um heatmap do Matomo e viu seu widget do Cal.com renderizado como uma fatia de 200 pixels com barra de rolagem interna, seu embed do YouTube encolhido num placeholder, e os marcadores de clique empilhados no topo do frame em vez de espalhados pelos campos do formulário abaixo, os dados de clique estão lá tecnicamente. O Matomo registrou as coordenadas certinho. O que está quebrado é o iframe na captura de tela que fica embaixo deles. O embed nunca expandiu para a altura do seu conteúdo quando o servidor do Matomo re-renderizou a página, então qualquer insight sobre abandono de formulário, seleção no calendário ou cliques em CTAs dentro do iframe acaba empilhado em cima de si mesmo num retângulo minúsculo.
A correção mais rápida é uma extensão gratuita do Chrome que mantemos, chamada Matomo Heatmap Helper. Ela força cada iframe a ter uma altura razoável durante a captura (scrollHeight real para embeds de mesma origem, pelo menos 800px para os cross-origin) e restaura o estilo original depois. O restante deste post explica como fazer a mesma coisa sem extensão, além das correções permanentes que sobrevivem a qualquer captura futura.
Como corrigir sem a extensão
Tem um snippet de console que resolve o problema imediatamente antes de cada captura, e algumas correções permanentes que você pode embutir junto com o embed. Escolha a que melhor se encaixa nas restrições do iframe com que você está lidando.
Diagnostique o que você está vendo
Antes de mudar qualquer coisa, verifique se o iframe é de mesma origem (você consegue ler o conteúdo dele) ou cross-origin (não consegue). Abra a página no Chrome, pressione F12 para abrir o DevTools, clique no iframe no painel Elements para que ele vire $0, depois mude para o Console:
// Paste in the console after selecting the iframe in the Elements panel
console.log('Visible:', $0.getBoundingClientRect().height);
try { console.log('Content:', $0.contentDocument.body.scrollHeight); }
catch (e) { console.log('Content: cross-origin, can\'t read'); }Se os dois números forem diferentes, essa é a diferença entre o que o visitante viu e o que o Matomo vai capturar. Se a segunda linha mostrar cross-origin, a página pai não consegue ler o conteúdo do embed e a maioria dos snippets abaixo vai pular ele. Nesses casos, force uma altura mínima.
Redimensione iframes de mesma origem pelo console
Se o seu iframe estiver na mesma origem da página (um formulário self-hosted, um app interno embutido no seu site de marketing, um produto irmão num subdomínio que compartilha sua origem via document.domain), a página pai consegue ler a altura do conteúdo diretamente. Cole isso logo antes de disparar a captura do heatmap:
// Paste in the console. Resizes every same-origin iframe to its content height.
document.querySelectorAll('iframe').forEach(f => {
try {
const doc = f.contentDocument;
if (doc) f.style.height = doc.body.scrollHeight + 'px';
} catch (e) { /* cross-origin, skip */ }
});Pronto para os de mesma origem. O navegador bloqueia o acesso a contentDocument em iframes cross-origin, então qualquer widget de fornecedor é silenciosamente ignorado. Rode o snippet de diagnóstico de novo para confirmar que as alturas estão certas, depois dispare a captura.
Force uma altura mínima em iframes cross-origin
Para Cal.com, Calendly, YouTube, Stripe Checkout e qualquer outro embed de forneceiro, a página pai não consegue ler o conteúdo do iframe. Você ainda pode definir uma altura suficientemente grande por fora, que é o melhor que dá pra fazer sem a cooperação do site embutido:
// Edit MIN_HEIGHT to fit your tallest embed (e.g. 800 for Cal.com, 1200 for long forms).
const MIN_HEIGHT = 800;
document.querySelectorAll('iframe').forEach(f => {
if (f.getBoundingClientRect().height < MIN_HEIGHT) {
f.style.height = MIN_HEIGHT + 'px';
}
});Isso é um chute, não uma medição. Coloque um valor generoso. Um iframe um pouco alto demais renderiza espaço em branco abaixo do conteúdo, o que é inofensivo. Um iframe curto demais ainda corta o fundo do formulário. Usamos 800 para widgets de agendamento e 1200 para formulários longos, ajustando por página se um embed específico precisar de mais.
Correção permanente quando você controla os dois lados: postMessage
Se o iframe vive num domínio que você controla (seu próprio subdomínio, um produto irmão, uma ferramenta interna), a solução mais limpa é um pequeno handshake via postMessage. A página filha reporta sua altura toda vez que o conteúdo muda, a página pai ouve e aplica. Sem chutes.
Na página filha embutida:
// Add to the iframe's child page
const sendHeight = () => parent.postMessage(
{ type: 'iframe-height', height: document.body.scrollHeight },
'*'
);
new ResizeObserver(sendHeight).observe(document.body);Na página pai:
// Add to the parent page
window.addEventListener('message', e => {
if (e.data?.type !== 'iframe-height') return;
document.querySelectorAll('iframe').forEach(f => {
if (f.contentWindow === e.source) f.style.height = e.data.height + 'px';
});
});Isso sobrevive a conteúdo carregado de forma lazy, trocas de fonte e etapas dinâmicas de formulário. Também melhora a experiência dos seus usuários reais, porque o iframe para de precisar da própria barra de rolagem.
Use o snippet de embed oficial do fornecedor
Cal.com, Calendly e HubSpot todos incluem um protocolo de redimensionamento via postMessage nos seus snippets de embed oficiais. Se você colou um <iframe src="..."> simples de uma página de docs ou de uma thread de suporte, troque pelo script pronto do fornecedor. Eles já resolveram isso pra você.
Para o Cal.com, é o pacote @calcom/embed-snippet ou a tag <script> do construtor de embed deles. Para o Calendly, é o snippet de embed inline em calendly.com. Para o HubSpot, é o código de embed do formulário na aba "Share" do formulário. Iframes simples apontando para a URL renderizada são o padrão comum que quebra aqui, e quase sempre é um erro de copiar e colar, não uma escolha deliberada.
iframe-resizer para embeds cross-origin arbitrários que você controla
Se você controla os dois lados mas não quer escrever o protocolo postMessage por conta própria, o pacote npm iframe-resizer cuida dos casos extremos (conteúdo lazy, fontes que carregam tarde, peculiaridades do iOS). Dois scripts, um na página pai, um na filha. Mesmo efeito do snippet acima, com mais testes em diferentes navegadores.
Substitua o iframe onde possível
Para YouTube e Vimeo em páginas com heatmap, troque o iframe por uma imagem de poster e só insira o iframe ao clicar. O visitante ainda assiste ao vídeo, e o heatmap captura um alvo de clique limpo e em tamanho real em vez de um placeholder minúsculo. O lite-youtube-embed é a versão canônica desse padrão, e também é mais rápido para usuários reais porque o player pesado do YouTube só carrega quando alguém realmente quer.
Isso não ajuda para widgets de agendamento ou iframes de pagamento, onde o embed é a própria interação. Mas ajuda para vídeo, timelines de redes sociais e qualquer coisa em que o iframe seja decorativo até ser clicado.
Por que o Matomo não consegue dimensionar seus iframes
Mesma causa raiz que os problemas de fontes e imagens, com uma complicação extra. A captura de tela do Matomo é um processo em duas etapas. Primeiro, o tracker serializa seu DOM em HTML e envia para o servidor. Depois, seu servidor Matomo re-renderiza esse HTML para produzir a captura que você vê na visualização do heatmap. Sem sessão de navegador, sem cookies, sem Referer apontando pro seu domínio. Só um arquivo HTML sendo renderizado a frio a partir de um IP diferente.
Para iframes, essa re-renderização captura o que o embed mostra no estado padrão. Um iframe é um documento separado. O navegador impede que a página pai redimensione automaticamente um iframe cross-origin, então a altura que você vê em produção é o que o código de embed do fornecedor negociou via postMessage em tempo de execução, mais qualquer altura CSS que seu time definiu para a UX em produção. O renderer do Matomo não executa JavaScript por tempo suficiente para esses handshakes chegarem. Iframes de mesma origem tecnicamente podem ser medidos, mas a maioria dos times depende de uma altura CSS definida para a UX em produção e não para uma captura estática, então o renderer acaba com um frame colapsado de qualquer jeito.
O que está falhando de fato
Alguns padrões que encontramos repetidamente:
- Tags
<iframe src="https://cal.com/...">simples coladas em um CMS em vez do script de embed oficial do Cal.com. O script oficial conecta o postMessage. O iframe simples não. - Embeds inline do Calendly onde a altura não está definida em nenhum lugar no CSS. O próprio snippet do Calendly define. Um iframe manual não.
- Formulários do HubSpot e Marketo que carregam de forma assíncrona depois que a página está interativa. O formulário pode ter 600px de altura em produção e 0px no momento da captura, porque o JavaScript do formulário ainda não tinha injetado os campos quando o Matomo serializou o DOM.
- Iframes do YouTube e Vimeo na proporção 16:9 padrão para uma coluna de 560 pixels renderizada pelo Matomo com um viewport diferente, acabando amassados.
- Stripe Checkout e outros iframes de pagamento que intencionalmente ficam num frame pequeno padrão e se recusam a expandir por razões de segurança. O fornecedor embutido decide a altura. Você não.
- Iframes de mesma origem com
height: autodefinido no CSS, que o navegador calcula corretamente em tempo de execução mas que o servidor de renderização estática trata como 0 porque não executa o layout pass por tempo suficiente.
Se o seu iframe está colapsado no heatmap, você está enfrentando pelo menos um desses problemas. Embeds de formulário geralmente enfrentam dois.
Essa é a mesma família de problemas que quebra fontes, imagens, containers com scroll e headers sticky na mesma captura. Escrevemos um post mais completo sobre capturas de tela quebradas no Matomo que cobre o resto, além de posts separados sobre por que as fontes não carregam e por que as imagens não carregam, já que esses aparecem quase com a mesma frequência.
O que faríamos na prática
Se o iframe vive no seu próprio domínio, implemente o handshake postMessage. Corrige o heatmap, melhora o embed em produção, e você para de brigar com layout shifts pra sempre.
Se for um embed de fornecedor, use o snippet oficial do fornecedor. Cal.com, Calendly e HubSpot incluem um protocolo de redimensionamento funcional nos deles. A maioria dos problemas que vemos é pessoas colando a URL renderizada do iframe em vez do script de embed.
Se nenhum dos dois for viável (chat de terceiros, iframes de pagamento, widgets de agendamento de fornecedores que você não pode alterar), a extensão Matomo Heatmap Helper para Chrome é o que usamos em sites de clientes onde não conseguimos mudar o stack. Ela força uma altura razoável durante a captura (scrollHeight real para mesma origem, pelo menos 800px para cross-origin) e restaura o estilo original depois, sem afetar a página em produção. Gratuita, open source, código no GitHub.
O Martez é o projeto maior do qual a extensão surgiu. Ele conecta o Matomo com Meta Ads e Google Ads para que ROAS, CLV e atribuição fiquem ao lado da sua análise web em vez de numa planilha separada. Está em beta privado. Entre na lista de espera se isso for relevante pra você.
Na maioria das vezes, é uma troca de snippet do fornecedor ou um handshake postMessage de distância. Vale a pena configurar uma vez.