地図アプリを開発していると、クラスター表示(Leaflet.markercluster)と単体マーカーのクリックが競合する問題に出会うことがあります。
今回、公園ナビの検索結果ページでもまさにこれが発生し、想定外の動作を引き起こしていました。
この記事では、発生した問題 → 原因 → 解決策 → 実際のコードという流れでわかりやすく整理します。
同じような構成のアプリを開発している方の参考になれば幸いです。
■ 発生した問題
症状:マーカーをクリックすると、クラスター側のイベントまで発火してしまう
markercluster を使用していたのですが、
- マーカーをクリックしたいのにクラスターが反応してズームされる
- 公園カードから特定のマーカーへジャンプすると、クラスターが展開されずポップアップが正常に開かない
- マーカーがクラスターに属している時と単体時で挙動が不安定になる
という問題が発生しました。
■ 原因
1. マーカークリックイベントの“イベント伝播”がクラスター側にも届いてしまう
Leaflet.markercluster は内部で「クリック時にクラスターをズームする」というイベントを持っています。
そのため、マーカー側で click を拾っても イベントが上位に伝播してしまい、クラスターのクリックイベントも同時に動いてしまうのです。
2. 公園カードからのフォーカス時、ズームが足りずクラスターが展開されない
Leaflet.markercluster には disableClusteringAtZoom の設定があります。
disableClusteringAtZoom: 18これは「ズーム18以上でクラスターを解除する」という意味ですが、カードからフォーカスする際にズームが不十分だと、クラスターが展開される前にポップアップを開こうとして失敗することがありました。
■ 解決方法
① イベント伝播を止める(stopPropagation の利用)
マーカークリックイベント内で以下のように伝播を止めます。
marker.on("click", function (e) {
// クラスターへの伝播を防ぐ
if (e.originalEvent) {
e.originalEvent.stopPropagation();
}
});これにより、
- マーカークリックイベントだけが実行される
- クラスターの “zoom into cluster” が発動しない
という、想定通りの挙動になりました。
② クラスターが展開されるズームまで強制的にズームアップ
公園カードからマーカーにフォーカスする際、ズームが低いとクラスターが展開されません。
そこで、以下の処理を追加しました。
// 現在のズームレベルを取得
const currentZoom = map.getZoom();
// クラスターが確実に展開されるズームに設定(18以上)
const targetZoom = Math.max(currentZoom, 18);
map.setView(latLng, targetZoom);
// ズーム完了後にポップアップを表示
setTimeout(function() {
marker.openPopup();
}, 400);ズームを十分に上げ → クラスターを展開 → その後にポップアップ表示
という流れにすることで、安定した挙動が得られました。
■ 得られた改善
上記2つの対処によって、次の改善が実現しました。
- マーカークリック時に勝手にズームされることがなくなった
- クラスターとマーカーのイベント干渉が消えた
- 公園カードからマーカーへフォーカスした際の UI が確実に動くようになった
- マーカーのポップアップが安定して開くようになった
地味ですが、ユーザー体験に直結する重要な改善です。
■ 同じ問題が起きたときのチェックリスト
Leaflet.markercluster を使う人向けに、再発防止のチェックリストも載せます。
✔ マーカークリックに stopPropagation() を入れたか?
イベント伝播はほぼ確実に衝突の原因になります。
✔ disableClusteringAtZoom の設定と実際のズーム処理が一致しているか?
クラスター解除の最低ズームを理解しておくことが重要。
✔ マーカーへジャンプする処理は「ズーム後にポップアップ表示」しているか?
ズーム完了前に openPopup すると失敗します。
■ まとめ
Leaflet.markercluster は便利ですが、
- イベント伝播
- クラスター解除ズーム
- ポップアップ表示タイミング
の3つを理解していないと、意図しない挙動が easily 発生します。
今回の修正は、まさにその3点を正しくコントロールすることで解決した例でした。
コメント