Matomoヒートマップが途中で切れる原因と修正方法

サイドバー、タブパネル、高さが制限されたコンテナがキャプチャ中にオーバーフローをクリップすると、Matomoヒートマップのスクリーンショットが特定の行から下で真っ黒または空白になります。クリック自体は正しく記録されていますが、空白の上に乗っているだけです。原因と対処法を解説します。

Matomoのヒートマップを開いたら、ある行から下が黒い四角に置き換わっていて、サイドバーは画面上の高さでたたまれ、クリックマーカーはフラットな色の上に積み重なっていた、という経験があるなら、クリック自体は問題ありません。Matomoは正しい座標に記録しています。壊れているのは、その下にあるスクリーンショットです。ライブページ上で独自の高さを制限してスクロール領域内にコンテンツをオーバーフローさせていたコンテナ(サイドバー、タブパネル、FAQのスライドオーバーなど)が、キャプチャされたDOMに完全なコンテンツを含められなかったのです。コンテナはスクリーンショットのパス中も制限された高さを保ったまま、Matomoはその先の領域を空白として描画しました。

最速の修正は、私たちがメンテナンスしている無料のChrome拡張機能 Matomo Heatmap Helper です。各キャプチャの直前に、ページ上のすべての要素を走査して scrollHeightclientHeight を超えているものを見つけ、スクリーンショットが完全なコンテンツを見られるように heightmax-height を書き換えます。キャプチャが終わると元の値を復元します。この記事の残りでは、拡張機能を使わずに同じことをする方法と、本番に投入する価値のある恒久的なCSSパターンを紹介します。

拡張機能なしで修正する方法

各キャプチャの直前に問題を回避するコンソールのスニペットと、実際のユーザーのUXを壊さずに使えるクエリパラメータでゲートされたCSSパターンがあります。スタックに合う方を選んでください。

問題のある要素を特定する

何かを変更する前に、クリップしているコンテナを探しましょう。ページをChromeで開き、F12でDevToolsを開き、Consoleに切り替えて以下を貼り付けてEnterを押します。5ピクセル程度以上の差が出た要素が候補です:

js
// 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));

わずかな差(1〜2ピクセル)はサブピクセルの丸め誤差で、問題ではないことがほとんどです。数百〜数千ピクセルのクリップコンテンツが報告されているものが本丸です。そこでヒートマップが暗くなっています。

手っ取り早い修正:クリップされたコンテナをすべて強制展開する

Matomoのヒートマップキャプチャをトリガーする直前にブラウザのコンソールに貼り付けるスニペットです。ページ上のすべての要素を走査し、コンテンツがオーバーフローしているものを展開して、オーバーフルールをオフにすることで完全なコンテンツを可視状態にします。MatomoがDOMをシリアライズするころには、クリップされていたセクションが展開済みになっています。

js
// 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';
  }
});

キャプチャをトリガーする前に、まだクリップされているものがないか確認するには:

js
// Returns the number of elements still clipping their contents. Should be zero.
Array.from(document.querySelectorAll('*'))
  .filter(el => el.scrollHeight > el.clientHeight + 1).length;

ゼロなら問題ありません。そうでない場合、残っている要素はシャドウルートやクロスオリジンiframeの内部にあることが多く、スニペットが届きません。詳しくは後述します。

これが手早い方法です。一度きりのキャプチャや、今すぐきれいなスクリーンショットが必要でコード変更を待てないレビューサイクルに使えます。定期的に再キャプチャするページには、以下の恒久的な修正を投入しましょう。

恒久的な修正:ヒートマップ専用スタイルシート

ポイントは、実際のユーザーのUXをそのまま保ちながら(スティッキーサイドバー、スクロール可能なタブパネル、存在理由のあるもの全部)、ヒートマップのパスだけに完全なコンテンツを見せることです。クエリパラメータでゲートされたCSSクラスがもっともシンプルな方法です:

css
/* 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;
}
js
// 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');
}

これで https://your-site.com/page?heatmap=1 にアクセスするとキャプチャフレンドリーモードになります。実際のユーザーはパラメータなしでページにアクセスするので、通常のスクロールUXはそのまま。同じサイト、2つのレイアウト、1つのクエリパラメータで切り替えます。

URLにトリガーを露出させたくない場合は、管理者専用ルートからセットするクッキーチェックや、Matomoのスクリーンショットフェッチャーに対するUser-Agentチェックに置き換えられます(アクセスログでMatomoのレンダラーが使っている正確なUA文字列をgrepして、サーバーサイドでゲートする)。考え方は同じで、ゲートの方法が変わるだけです。

トレードオフ

max-height: none をすべての訪問者に適用しないでください。スティッキーサイドバー、スクロール可能なタブパネル、高さ制限のあるドロワーには存在理由があります。ナビゲーションを表示し続け、パネルをレイアウトの他の部分と共存させ、4,000ピクセル級のアコーディオンがページを占拠しないようにするためのものです。オーバーライドをゲートする意味は、実際のユーザーが期待する本番環境を維持しつつ、キャプチャ時だけレイアウトを切り替えることにあります。

テスト済みのワークフロー:シークレットウィンドウで ?heatmap=1 付きのページを開き、クリックしてヒートマップセッションを記録し、Matomoのキャプチャが発火するまでタブを開けたままにします。同じページを通常セッションで開いている実際のユーザーにはオーバーライドは届きません。

Matomoがカットオフより先をキャプチャできない理由

Matomoのスクリーンショットキャプチャは2段階のプロセスです。まず、トラッカーがライブDOMをHTMLにシリアライズして送り出します。次に、Matomoサーバーがそのページのヒートマップビューに表示するスクリーンショットを生成するためにHTMLを再レンダリングします。レンダラーはシリアライズされたDOMの記述通りにページを描画します。DOMのコンテナに height: 600pxoverflow: hidden があって、実際のコンテンツが2,000ピクセルの高さだとしたら、上から600ピクセルしか画像に収まりません。それ以下はコンテナの境界でクリップされ、ヒートマップキャンバスはその先の領域を空白(下にあるものによって黒、白、または透明)として描画します。

クリック座標はドキュメントレベルで追跡されるので、クリッピングを問題なく生き延びます。クリックが指していたピクセルは生き延びません。

実際に何が失敗しているのか

よく遭遇するパターンをいくつか:

  • max-height: calc(100vh - header)overflow-y: auto を持つサイドバー。メガナブ、ファセットフィルター、チャットパネルでよく見られます。サイドバーはライブページではビューポートより高いのに、スクリーンショットにはビューポート高さのスライスしか映りません。
  • [role=tabpanel] が独自のスクロールコンテナを持つタブパネル。Bootstrap、Tailwind UI、MUI、ほとんどのコンポーネントライブラリのデフォルト動作です。アクティブなタブは制限された高さでクリップされ、非アクティブなタブはまったく見えません。
  • 本物のモーダルではないドロワースタイルのセクション:スライドオーバー、展開フィルタートレイ、インラインカートパネル。意図的に 100vh または固定ピクセル高さに制限されているもの。コンテナはキャプチャ中も制限されたままです。
  • 内部スクローラーを持つダッシュボードカード。コンテンツが overflow-y: auto のdivの内側でオーバーフローする固定高さのタイル。Matomoは外側の寸法でタイルをスクリーンショットし、内部スクローラーの可視スライスより上のものはすべて消えます。
  • これを組み込んでいるコンポーネントライブラリ:shadcn/uiの ScrollArea、内部スクロールを持つMUIの Drawer、Tailwind UIのコンボボックスリスト、Headless UIのタブパネル。ページを監査するときに知っておく価値があります。問題のある要素は自分が書いていないラッパーの3層奥にいることが多いです。
  • 寸法が制限されたiframe。iframeのDOMは外側のiframeサイズでクリップされ、Matomoはクロスオリジンiframeの内部にアクセスできません。

ヒートマップが特定の行から暗くなっているなら、これらのうち少なくとも1つに当たっています。コンテンツサイドバーのあるほとんどのページは2つ同時に当たっています。

これは同じスクリーンショットでフォント、画像、スティッキーヘッダーが壊れる問題と同じ系統のものでもあります。残りを解説したMatomoヒートマップスクリーンショットが壊れる原因の詳細記事や、Matomoヒートマップでフォントが読み込まれない理由Matomoヒートマップで画像が読み込まれない理由の個別記事も書いています。

私たちが実際にやること

テンプレートを管理しているなら、ゲートされたスタイルシートを投入します。セレクターいくつかとクエリパラメータチェックだけで、Matomoヒートマップを使っている間ずっと本番に残り続けます。5分の作業で永続します。

できない場合は、コンソールスニペットが一度きりのキャプチャで同じカバレッジを提供します。ただし、毎回のキャプチャ前に貼り付けることを忘れないようにしなければなりません。

どちらも届かない場合(CMSロックされたテンプレート、自分が所有していないサードパーティウィジェット、CSSルールもscriptタグも追加できないあらゆる状況)、Matomo Heatmap Helper Chrome拡張機能が、スタックを変更できないクライアントサイトで私たちが使っているものです。キャプチャ中に同じ scrollHeight/maxHeight の書き換えを実行して終了後に元に戻し、さらにフォント、画像、スティッキーヘッダーの修正も同様に行います。無料、オープンソース、GitHubでコード公開

Martez は拡張機能が生まれた大本のプロジェクトです。MatomoとMeta AdsおよびGoogle Adsを接続して、ROAS、CLV、アトリビューションを別のスプレッドシートではなくWebアナリティクスの隣に並べます。現在プライベートベータ中です。関係がありそうならウェイトリストに登録してください。

セレクターいくつかとクエリパラメータ。一度やる価値はあります。

Related Articles

Matomoヒートマップが途中で切れる原因と修正方法 - Martez Blog