日本語版の記事はこちら (Japanese version is here)
Recently, I published the Google Maps Area Editor, which allows you to transform and rotate shapes just like in PowerPoint.
Actually, while implementing this component, one of the things I struggled with the most (and had to get creative about) was “restricting the drag movement area of the markers.”
If you’ve ever tried to build a custom UI with the Google Maps JavaScript API, you’ve probably faced this issue. I’m sharing the solution (hack) as a memorandum.
The Problem with the Standard API’s “Drag Feature”
When implementing anchors (the circles at the four corners) for resizing shapes or handles for rotating them, you naturally want users to interact with them by dragging them with a mouse.
With the latest AdvancedMarkerElement, you can easily make a marker draggable just by setting gmpDraggable: true. However, there is a major problem here. With the standard feature, the marker can be freely moved anywhere on the map.
Anchors that stretch the sides of a shape should ideally move only “along a specific straight line,” and rotation handles must be restricted to move “along a specific circumference.” If they aren’t, the UI on the screen will completely break down.
The Solution: Separating the Visible Marker from the Transparent Operation Marker
If you try to “forcibly overwrite and fix the coordinates during a drag” using a single marker, the position of the mouse cursor and the actual drawn position of the marker will become misaligned, making the event behavior highly unstable.
The solution I arrived at is the technique of “overlaying a transparent marker for receiving drag events on top of the marker responsible for the actual visual appearance.”
Implementation Concept
By completely separating the roles, you can smoothly achieve dragging with complex constraint conditions.
- Drawing Marker (Visible): Not draggable (
gmpDraggable: false). It is always programmatically placed at the “correct calculated position.” - Operation Marker (Transparent): Draggable. It is hidden using CSS
opacity: 0. The user freely drags this marker.
Actual Code Example
Here is an excerpt (concept) of the core logic when implemented in Vanilla JS.
(In reality, I added a few more tweaks in the production code.)
// 1. The marker responsible for the actual appearance (Not draggable)
const visibleMarker = new google.maps.marker.AdvancedMarkerElement({
map: map,
content: createMarkerSvg(), // SVG or other elements you want to display
gmpClickable: false, // Click events are also unnecessary
zIndex: 200,
});
// 2. The transparent marker that receives drag operations (Draggable)
const draggableMarker = new google.maps.marker.AdvancedMarkerElement({
map: map,
content: createDraggableContent(12), // e.g., a div element for hit detection
gmpDraggable: true,
zIndex: 201, // Placed above the visible marker
});
// Hide it from the screen by making it transparent
draggableMarker.style.opacity = "0";
// 3. Monitor drag events and correct coordinates
draggableMarker.addListener("drag", (event) => {
// Get the raw coordinates the user is currently dragging
const rawLatLng = event.latLng;
// Correct to coordinates on a movable straight line or circumference using custom vector calculations
const correctedLatLng = calculateRestrictedPosition(rawLatLng);
// Move the drawing marker to the corrected "proper position"
visibleMarker.position = correctedLatLng;
// * If necessary, redraw the shape (Polygon) itself here as well
});
Conclusion
By using this “transparent marker hack,” you can achieve “rich UI operations with restricted degrees of freedom,” which is difficult to do with just the standard features of the Google Maps API.
Nowadays, state management is often hidden by frameworks like React, but knowing these gritty Vanilla JS DOM/API hacks is extremely useful because they are highly applicable in many situations.
The full source code for the actual area editor incorporating this logic is available on GitHub (google-maps-area-editor). If you are interested, please take a look along with the mathematical vector calculations (math.js).