先日、Googleマップ上でPowerPointのように図形を変形・回転できるエリアエディタを公開しました。
実はこのコンポーネントを実装する中で、地味に苦労した(そして工夫した)のが、「マーカーのドラッグ移動範囲の制限」です。
Google Maps JavaScript APIでカスタムUIを作ろうとしたことがある方なら直面する問題かと思いますが、備忘録も兼ねてその解決手法(ハック)を紹介します。
標準APIの「ドラッグ機能」が抱える課題
図形をリサイズするためのアンカー(四隅の丸印)や、回転させるためのハンドルを実装する際、当然ユーザーにはそれらをマウスでドラッグして操作してもらいます。
最新の AdvancedMarkerElement では gmpDraggable: true を設定するだけで簡単にマーカーをドラッグ可能にできます。しかし、ここに大きな問題があります。標準機能では、マーカーが地図上のどこへでも自由に移動できてしまうのです。
図形の辺を伸縮させるアンカーは「特定の直線上」だけを動いてほしいですし、回転ハンドルは「特定の円周上」を動くように制限されなければ、画面上のUIとして破綻してしまいます。
解決策:見た目のマーカーと、操作用の透明マーカーを分離する
「ドラッグ中の座標を強制的に上書きして固定する」といった処理を単一のマーカーで行おうとすると、マウスカーソルの位置とマーカーの実際の描画位置がズレてしまい、イベントの挙動が非常に不安定になります。
そこで辿り着いたのが、「実際の見た目を担当するマーカー」の上に、「ドラッグイベントを受け取るための透明なマーカー」を重ねて配置するという手法です。
実装のイメージ
役割を完全に分離することで、複雑な拘束条件付きのドラッグを滑らかに実現します。
- 描画用マーカー: ドラッグ不可(
gmpDraggable: false)。常に「計算上の正しい位置」にプログラムから配置される。 - 操作用(透明)マーカー: ドラッグ可能。CSSで
opacity: 0にして隠しておく。ユーザーはこれを自由にドラッグする。
実際のコード例
Vanilla JSで実装した場合のコアとなるロジックの抜粋(イメージ)です。
(実際はもう少し色々と小細工しています。)
// 1. 実際の見た目を担当するマーカー(ドラッグ不可)
const visibleMarker = new google.maps.marker.AdvancedMarkerElement({
map: map,
content: createMarkerSvg(), // 表示したいSVG等の要素
gmpClickable: false, // クリックイベントも不要
zIndex: 200,
});
// 2. ドラッグ操作を受け付ける透明なマーカー(ドラッグ可能)
const draggableMarker = new google.maps.marker.AdvancedMarkerElement({
map: map,
content: createDraggableContent(12), // 当たり判定用のdiv要素など
gmpDraggable: true,
zIndex: 201, // 描画マーカーより上に配置
});
// 透明にして画面から隠す
draggableMarker.style.opacity = "0";
// 3. ドラッグイベントの監視と座標補正
draggableMarker.addListener("drag", (event) => {
// ユーザーがドラッグしている生の座標を取得
const rawLatLng = event.latLng;
// 独自のベクトル計算などで、移動可能な直線・円周上の座標に補正する
const correctedLatLng = calculateRestrictedPosition(rawLatLng);
// 描画用のマーカーを、補正された「正しい位置」に移動させる
visibleMarker.position = correctedLatLng;
// ※必要に応じて、図形(Polygon)自体の再描画もここで行う
});
まとめ
この「透明マーカーによるハック」を使うことで、Google Maps APIの標準機能では難しい「自由度を制限したリッチなUI操作」が可能になります。
最近はReactなどのフレームワークで状態管理を隠蔽してしまうことが多いですが、こういったVanilla JSでの泥臭いDOM / APIハックは、応用が効くので知っておくと非常に便利です。
このロジックを組み込んだ実際のエリアエディタの全ソースコードは、GitHub (google-maps-area-editor) で公開していますので、数学的なベクトル計算(math.js)等と合わせて興味がある方は覗いてみてください。