Matomoヒートマップを開いたとき、Cal.comウィジェットが内部スクロールバー付きの200ピクセルのスリット状で表示され、YouTubeの埋め込みがプレースホルダーに縮小され、クリックマーカーがフォームフィールド全体に広がるのではなくフレームの上部に積み重なっていた経験はないでしょうか。クリックデータ自体は正確に記録されています。Matomoは座標を正しく記録しています。壊れているのは、その下のスクリーンショットのiframeです。MatomoサーバーがページをRe-renderする際、埋め込みがコンテンツの高さに合わせて展開されなかったため、フォームの離脱、カレンダーの選択、iframe内のCTAクリックに関するあらゆる分析が、小さな矩形の中に積み重なってしまいます。
最も手軽な修正方法は、私たちが開発した無料のChrome拡張機能Matomo Heatmap Helperです。キャプチャ時にすべてのiframeを適切な高さに強制します(同一オリジンの埋め込みには実際の scrollHeight、クロスオリジンのものには最低800px)し、その後元のスタイルに戻します。この投稿の残りは、拡張機能を使わずに同じことを行う方法と、将来のすべてのキャプチャで有効な恒久的な修正についてです。
拡張機能なしで修正する方法
キャプチャの直前に問題を一時的に解消するコンソールのスニペットと、埋め込みとともに本番環境に適用できる恒久的な修正がいくつかあります。対象のiframeの制約に合うものを選んでください。
状況を診断する
何かを変更する前に、iframeが同一オリジン(コンテンツを読み取れる)かクロスオリジン(読み取れない)かを確認してください。ChromeでページをURL、F12を押してDevToolsを開き、Elementsパネルでiframeをクリックして $0 にしてから、Consoleに切り替えます:
// ElementsパネルでiframeをクリックしてからConsoleに貼り付ける
console.log('Visible:', $0.getBoundingClientRect().height);
try { console.log('Content:', $0.contentDocument.body.scrollHeight); }
catch (e) { console.log('Content: cross-origin, can\'t read'); }2つの数値が異なる場合、それが訪問者が見たものとMatomoがキャプチャするものとのズレです。2行目が cross-origin と表示された場合、親ページは埋め込みのコンテンツを読み取れないため、以下のスニペットのほとんどはスキップされます。その場合は代わりに最小高さを強制する方法を使ってください。
コンソールから同一オリジンiframeをリサイズする
iframeがページと同一オリジンにある場合(セルフホストのフォーム、マーケティングサイトに埋め込まれた内部アプリ、document.domain でオリジンを共有するサブドメインの姉妹プロダクト)、親ページはコンテンツの高さを直接読み取れます。ヒートマップキャプチャをトリガーする直前に以下を貼り付けてください:
// Consoleに貼り付ける。すべての同一オリジンiframeをコンテンツの高さにリサイズする。
document.querySelectorAll('iframe').forEach(f => {
try {
const doc = f.contentDocument;
if (doc) f.style.height = doc.body.scrollHeight + 'px';
} catch (e) { /* cross-origin, skip */ }
});同一オリジンの場合はこれだけです。ブラウザはクロスオリジンiframeへの contentDocument アクセスをブロックするため、ベンダーのウィジェットはすべて暗黙的にスキップされます。診断スニペットを再度実行して高さが正しいことを確認してから、キャプチャをトリガーしてください。
クロスオリジンiframeに最小高さを強制する
Cal.com、Calendly、YouTube、Stripe Checkout、その他のベンダー埋め込みでは、親ページはiframeのコンテンツを読み取れません。外部から十分な高さを設定することは可能で、埋め込みサイトの協力なしにできる最善の方法です:
// MIN_HEIGHTを最も高い埋め込みに合わせて編集する(例:Cal.comなら800、長いフォームなら1200)
const MIN_HEIGHT = 800;
document.querySelectorAll('iframe').forEach(f => {
if (f.getBoundingClientRect().height < MIN_HEIGHT) {
f.style.height = MIN_HEIGHT + 'px';
}
});これは測定ではなく推測です。余裕を持って設定してください。少し高すぎるiframeはコンテンツの下に空白スペースが生じるだけで問題ありません。短すぎるiframeはフォームの下部が切れたままになります。私たちは予約ウィジェットには800、長いサインアップフォームには1200をデフォルトとし、特定の埋め込みにさらに高さが必要な場合はページごとに調整しています。
両方を制御している場合の恒久的な修正:postMessage
iframeが自分で管理するドメイン(自分のサブドメイン、姉妹プロダクト、内部ツール)にある場合、最もクリーンな解決策は小さなpostMessageのハンドシェイクです。子ページはコンテンツのリフローのたびに高さを報告し、親はそれを受け取って適用します。推測は不要です。
埋め込まれた子ページに追加:
// iframeの子ページに追加する
const sendHeight = () => parent.postMessage(
{ type: 'iframe-height', height: document.body.scrollHeight },
'*'
);
new ResizeObserver(sendHeight).observe(document.body);親ページに追加:
// 親ページに追加する
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';
});
});これは遅延読み込みコンテンツ、フォントの読み込み、動的なフォームのステップにも対応します。また、iframeが独自のスクロールバーを持つ必要がなくなるため、実際のユーザーの体験もスムーズになります。
ベンダーの公式埋め込みスニペットを使う
Cal.com、Calendly、HubSpotはいずれも公式埋め込みスニペットに組み込みのpostMessageリサイズプロトコルを搭載しています。ドキュメントページやサポートスレッドからベアの <iframe src="..."> をコピーして貼り付けた場合は、ベンダーのドロップインスクリプトに差し替えてください。すでに解決済みです。
Cal.comの場合は @calcom/embed-snippet パッケージか、埋め込みビルダーからの <script> タグです。Calendlyの場合はcalendly.comのインライン埋め込みスニペットです。HubSpotの場合はフォームの「共有」タブのフォーム埋め込みコードです。レンダリング済みURLを指すベアiframeがここで問題になる一般的なパターンで、意図的な選択ではなくほとんどの場合コピーペーストのミスです。
制御するクロスオリジン埋め込みへのiframe-resizer
両方を制御しているがpostMessageプロトコルを自分で書きたくない場合、iframe-resizer npmパッケージがエッジケース(遅延コンテンツ、遅延読み込みフォント、iOSの癖)を処理します。親に1つ、子に1つ、2つのスクリプトを設置するだけです。上のスニペットと同じ効果で、ブラウザ間でのテストが充実しています。
可能な場合はiframeを置き換える
ヒートマップ追跡ページのYouTubeとVimeoについては、iframeをポスター画像に差し替えて、クリック時にのみiframeを挿入するようにしてください。訪問者は引き続き動画を見ることができ、ヒートマップは小さなプレースホルダーの代わりにクリーンでフルサイズのクリックターゲットをキャプチャします。lite-youtube-embedはこのパターンの標準的な実装であり、重いYouTubeプレーヤーは実際に必要なときにしか読み込まれないため、実際のユーザーにとっても高速です。
これは予約ウィジェットや決済iframeには効果がありません。それらの埋め込みがインタラクション自体だからです。動画、ソーシャルタイムライン、クリックされるまで装飾的な要素には効果があります。
Matomoがiframeをサイジングできないなぜ
フォントや画像の問題と同じ根本的な原因で、もう1つ追加の問題があります。Matomoのスクリーンショットキャプチャは2段階のプロセスです。まず、トラッカーがライブのDOMをHTMLにシリアライズして送信します。次に、MatomoサーバーがそのHTMLをRe-renderして、ヒートマップビューで見るスクリーンショットを生成します。ブラウザセッションなし、Cookieなし、あなたのドメインを指すRefererなし。別のIPから冷たくRe-renderされるHTMLファイルだけです。
iframeについては、そのRe-renderは埋め込みがデフォルト状態で表示するものをキャプチャします。iframeは独立したドキュメントです。ブラウザは親がクロスオリジンiframeを自動サイジングするのをブロックするため、本番環境で見る高さはベンダーの埋め込みコードがランタイムでpostMessageを通じてネゴシエートしたものと、ライブUXのためにチームが設定したCSS高さの組み合わせです。MatomoレンダラーはJavaScriptをそれらのハンドシェイクが完了するほど長く実行しません。同一オリジンiframeは技術的に測定可能ですが、ほとんどのチームはライブUXのためにCSS高さを設定しており、静的スクリーンショット向けではないため、レンダラーはいずれにしてもデフォルトで折りたたまれたフレームになります。
実際に何が失敗しているか
私たちがよく目にするパターンをいくつか挙げます:
- CMSに貼り付けられたベアの
<iframe src="https://cal.com/...">タグ(Cal.comの公式埋め込みスクリプトの代わりに)。公式スクリプトはpostMessageを設定します。ベアiframeは設定しません。 - CSSのどこにも高さが設定されていないCalendlyインライン埋め込み。Calendly独自のスニペットは設定します。手作りのiframeは設定しません。
- ページがインタラクティブになった後に非同期で読み込まれるHubSpotとMarketoのフォーム。フォームは本番では600px高さになるかもしれませんが、MatomoがDOMをシリアライズしたときにフォームのJavaScriptがまだフィールドを注入していなかったため、キャプチャ時は0pxです。
- Matomoが異なるビューポートでRe-renderすると、560ピクセル幅のカラム向けデフォルト16:9比率のYouTubeとVimeoのiframeが押しつぶされて表示される。
- セキュリティ上の理由から意図的に小さなデフォルトフレームに固定され、展開を拒否するStripe Checkoutとその他の決済iframe。埋め込みベンダーが高さを決定します。あなたには決定権がありません。
- CSSで
height: autoが設定された同一オリジンiframe。ブラウザはランタイムで正しく計算しますが、静的レンダリングサーバーはレイアウトパスを十分長く実行しないため0として扱います。
ヒートマップでiframeが折りたたまれている場合、これらのうち少なくとも1つに該当しています。フォームの埋め込みは通常2つに該当します。
これはフォント、画像、スクロールコンテナ、スティッキーヘッダーを同じスクリーンショットで壊す問題と同じ系統です。Matomoヒートマップスクリーンショットが壊れる原因と修正方法に関する長い投稿で残りを網羅しており、フォントが読み込まれない理由と画像が読み込まれない理由についての個別の投稿もほぼ同じくらいよく見かけます。
私たちが実際に行うこと
iframeが自分のドメインにある場合は、postMessageハンドシェイクを実装してください。ヒートマップが修正され、ライブ埋め込みがスムーズになり、レイアウトのずれと永遠に戦う必要がなくなります。
ベンダーの埋め込みであれば、ベンダーの公式スニペットを使ってください。Cal.com、Calendly、HubSpotはそれぞれ動作するリサイズプロトコルを搭載しています。私たちが目にする問題のほとんどは、埋め込みスクリプトの代わりにレンダリング済みiframe URLを貼り付けた結果です。
どちらも対応できない場合(サードパーティのチャット、決済iframe、埋め込みを変更できないベンダーの予約ウィジェット)、Matomo Heatmap Helper Chrome拡張機能が、スタックを変更できないクライアントサイトで私たちが使うものです。キャプチャ時に適切な高さを強制し(同一オリジンには実際の scrollHeight、クロスオリジンには最低800px)、その後元のスタイルに戻すため、ライブページには影響しません。無料、オープンソース、コードはGitHubで公開しています。
Martezは、この拡張機能が生まれた大きなプロジェクトです。MatomoをMeta AdsおよびGoogle Adsと接続し、ROAS、CLV、アトリビューションを別のスプレッドシートではなくWebアナリティクスの隣に配置します。現在プライベートベータ中です。関係があればウェイトリストへ参加してください。
ほとんどの場合、ベンダースニペットへの差し替えかpostMessageハンドシェイクで解決します。一度設定する価値はあります。