【開発ログ】Leaflet.markerclusterで発生した「マーカークリック時の競合問題」とその解決方法

地図アプリを開発していると、クラスター表示(Leaflet.markercluster)と単体マーカーのクリックが競合する問題に出会うことがあります。
今回、開発中のアプリの検索結果ページでもまさにこれが発生し、想定外の動作を引き起こしていました。

Leaflet.markerclusterの基本的な導入方法は、下記の記事にまとめています。

この記事では、発生した問題 → 原因 → 解決策 → 実際のコードという流れでわかりやすく整理します。同じような構成のアプリを開発している方の参考になれば幸いです。

発生した問題

markerclusterを使用していたのですが、次のような問題が発生しました。

  • マーカーをクリックしたいのにクラスターが反応してズームされる
  • 公園カードから特定のマーカーへジャンプすると、クラスターが展開されずポップアップが正常に開かない
  • マーカーがクラスターに属している時と単体時で挙動が不安定になる

原因

Leaflet.markercluster は内部で「クリック時にクラスターをズームする」というイベントを持っています。
そのため、マーカー側で click を拾っても イベントが上位に伝播してしまい、クラスターのクリックイベントも同時に動いてしまうのです。

解決策

① イベントの伝播を止める

Leaflet.markercluster には disableClusteringAtZoom の設定があります。

JavaScript
disableClusteringAtZoom: 18

これは「ズーム18以上でクラスターを解除する」という意味ですが、カードからフォーカスする際にズームが不十分だと、クラスターが展開される前にポップアップを開こうとして失敗することがありました。

マーカークリックイベント内で以下のように伝播を止めます。

JavaScript
marker.on("click", function (e) {
    // クラスターへの伝播を防ぐ
    if (e.originalEvent) {
        e.originalEvent.stopPropagation();
    }
});

これにより、マーカークリックイベントだけが実行され、クラスターの “zoom into cluster” が発動しなくなりました。想定通りの挙動です。

② ズーム完了を待ってからポップアップを開く

公園カードからマーカーにフォーカスする際、ズームが低いとクラスターが展開されません。そこで、以下の処理を追加しました。

JavaScript
// 現在のズームレベルを取得
const currentZoom = map.getZoom();

// クラスターが確実に展開されるズームに設定(18以上)
const targetZoom = Math.max(currentZoom, 18);

map.setView(latLng, targetZoom);

// ズーム完了後にポップアップを表示
setTimeout(function() {
    marker.openPopup();
}, 400);

ズームを十分に上げ → クラスターを展開 → その後にポップアップ表示
という流れにすることで、安定した挙動が得られました。

なおsetTimeoutの400msは決め打ちの待ち時間で、ズームアニメーションの完了を厳密に検知しているわけではありません。map.once("zoomend", ...)でズーム完了イベントを拾う方法のほうが本来はきれいですが、今回は素早く直したかったのでこの形に落ち着きました。

得られた効果

上記2つの対処によって、次の改善が実現しました。

  • マーカークリック時に勝手にズームされることがなくなった
  • クラスターとマーカーのイベント干渉が消えた
  • 公園カードからマーカーへフォーカスした際の UI が確実に動くようになった
  • マーカーのポップアップが安定して開くようになった

地味ですが、ユーザー体験に直結する重要な改善です。

まとめ:再発防止チェックリスト

Leaflet.markercluster を使う人向けに、再発防止のチェックリストも載せます。

  • イベント伝播:ほぼ確実に衝突の原因になる
  • クラスター解除ズーム:最低ズームを理解しておく
  • ポップアップ表示タイミング:ズーム完了前に openPopup すると失敗する

この3つを理解していないと、意図しない挙動が簡単に発生します。今回の修正は、まさにその3点を正しくコントロールすることで解決した例でした。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

普段は主にPython開発をしています。
最近はAI駆動開発にも関わっています。

コメント

コメントする