Skip to content

Commit 5f9a8f9

Browse files
committed
fix: CustomMarker mobile draggable
1 parent b6371fc commit 5f9a8f9

4 files changed

Lines changed: 292 additions & 167 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import { useRef } from "react";
2+
import { EventType, OverlayMarker } from "./type";
3+
4+
let classCache;
5+
6+
export const useOverlayMarker = () => {
7+
if (classCache) {
8+
return classCache;
9+
}
10+
11+
classCache = class OverlayMarkerImpl extends google.maps.OverlayView implements OverlayMarker {
12+
map: google.maps.Map;
13+
container: HTMLDivElement;
14+
position: google.maps.LatLngLiteral;
15+
draggable: boolean;
16+
preventDragOnClickable: boolean;
17+
eventMap = new Map<EventType, (position: google.maps.LatLngLiteral) => void>();
18+
mounseDownHandler?: (e: MouseEvent) => void;
19+
mouseMoveHandler?: (e: MouseEvent) => void;
20+
mouseUpHandler?: (e: MouseEvent) => void;
21+
mouseLeaveHandler?: (e: MouseEvent) => void;
22+
touchStartHandler?: (e: TouchEvent) => void;
23+
touchMoveHandler?: (e: TouchEvent) => void;
24+
touchEndHandler?: (e: TouchEvent) => void;
25+
touchCancelHandler?: (e: TouchEvent) => void;
26+
27+
constructor({
28+
map,
29+
content,
30+
position,
31+
draggable,
32+
preventDragOnClickable,
33+
}: {
34+
map: google.maps.Map;
35+
content: Node;
36+
position: google.maps.LatLng | google.maps.LatLngLiteral;
37+
draggable: boolean;
38+
preventDragOnClickable: boolean;
39+
}) {
40+
super();
41+
42+
this.position = position instanceof google.maps.LatLng ? position.toJSON() : position;
43+
this.container = content as HTMLDivElement;
44+
this.map = map;
45+
this.draggable = !!draggable;
46+
this.preventDragOnClickable = !!preventDragOnClickable;
47+
48+
this.setMap(map);
49+
}
50+
51+
onAdd() {
52+
this.setDraggable(this.draggable);
53+
54+
this.container.style.position = 'absolute';
55+
// this.container.appendChild(this.content);
56+
this.getPanes()?.overlayMouseTarget.appendChild(this.container);
57+
}
58+
59+
onRemove() {
60+
this.mouseMoveHandler && window.removeEventListener('mousemove', this.mouseMoveHandler);
61+
this.mouseUpHandler && window.removeEventListener('mouseup', this.mouseUpHandler);
62+
this.container.parentNode?.removeChild(this.container);
63+
}
64+
65+
draw() {
66+
const position = this.getProjection().fromLatLngToDivPixel(this.position);
67+
68+
if (position) {
69+
Object.assign(this.container!.style, {
70+
left: `${position.x}px`,
71+
top: `${position.y}px`,
72+
});
73+
}
74+
}
75+
76+
setDraggable(draggable: boolean) {
77+
this.container.draggable = this.draggable = draggable;
78+
this.container.style['touch-action'] = this.draggable ? 'none' : 'auto';
79+
80+
this.removeEvent();
81+
draggable && this.addEvent();
82+
}
83+
84+
setPreventDragOnClickable(preventDragOnClickable: boolean) {
85+
this.preventDragOnClickable = preventDragOnClickable;
86+
}
87+
88+
getCurrentCoordinate(e: MouseEvent | Touch) {
89+
const origin = this.get('origin');
90+
const left = origin.clientX - e.clientX;
91+
const top = origin.clientY - e.clientY;
92+
const pos = this.getProjection().fromLatLngToDivPixel(this.position)!;
93+
94+
return this.getProjection().fromDivPixelToLatLng(new google.maps.Point(pos.x - left, pos.y - top))?.toJSON()!;
95+
};
96+
97+
private addEvent() {
98+
const mapDiv = this.map.getDiv();
99+
let isDown = false;
100+
101+
// mouse event
102+
this.container.addEventListener(
103+
'mousedown',
104+
(this.mounseDownHandler = (e: MouseEvent) => {
105+
if (this.preventDragOnClickable && ['A', 'BUTTON'].includes((e.target as HTMLElement).tagName)) {
106+
return;
107+
}
108+
109+
isDown = true;
110+
this.map.set('draggable', false);
111+
112+
this.container.style.cursor = 'move';
113+
114+
this.set('origin', e);
115+
116+
window.addEventListener(
117+
'mousemove',
118+
(this.mouseMoveHandler = (e: MouseEvent) => {
119+
const latLng = this.getCurrentCoordinate(e);
120+
this.set('position', latLng);
121+
this.set('origin', e);
122+
this.draw();
123+
124+
this.eventMap.get('drag')?.(latLng);
125+
})
126+
);
127+
128+
this.eventMap.get('dragstart')?.(this.position);
129+
})
130+
);
131+
window.addEventListener(
132+
'mouseup',
133+
(this.mouseUpHandler = (e: MouseEvent) => {
134+
if (isDown) {
135+
isDown = false;
136+
this.map.set('draggable', true);
137+
138+
if (this.mouseMoveHandler) {
139+
window.removeEventListener('mousemove', this.mouseMoveHandler);
140+
this.container.style.cursor = 'default';
141+
142+
this.eventMap.get('dragend')?.(this.getCurrentCoordinate(e));
143+
}
144+
}
145+
})
146+
);
147+
mapDiv.addEventListener(
148+
'mouseleave',
149+
(this.mouseLeaveHandler = () => {
150+
google.maps.event.trigger(this.container, 'mouseup');
151+
})
152+
);
153+
154+
// touch event
155+
this.container.addEventListener(
156+
'touchstart',
157+
(this.touchStartHandler = (e: TouchEvent) => {
158+
if (this.preventDragOnClickable && ['A', 'BUTTON'].includes((e.target as HTMLElement).tagName)) {
159+
return;
160+
}
161+
162+
isDown = true;
163+
this.map.set('draggable', false);
164+
165+
this.container.style.cursor = 'move';
166+
167+
const touch = e.touches[0];
168+
this.set('origin', touch);
169+
170+
window.addEventListener(
171+
'touchmove',
172+
(this.touchMoveHandler = (e: TouchEvent) => {
173+
const touch = e.touches[0];
174+
175+
if (!touch) {
176+
return;
177+
}
178+
179+
const latLng = this.getCurrentCoordinate(touch);
180+
this.set('position', latLng);
181+
this.set('origin', touch);
182+
this.draw();
183+
184+
this.eventMap.get('drag')?.(latLng);
185+
})
186+
);
187+
188+
this.eventMap.get('dragstart')?.(this.position);
189+
})
190+
);
191+
window.addEventListener(
192+
'touchend',
193+
(this.touchEndHandler = (e: TouchEvent) => {
194+
if (isDown) {
195+
if (e.cancelable) {
196+
e.preventDefault();
197+
}
198+
199+
isDown = false;
200+
this.map.set('draggable', true);
201+
202+
if (this.touchMoveHandler) {
203+
window.removeEventListener('touchmove', this.touchMoveHandler);
204+
this.container.style.cursor = 'default';
205+
206+
const touch = e.touches[0];
207+
208+
if (touch) {
209+
this.eventMap.get('dragend')?.(this.getCurrentCoordinate(touch));
210+
}
211+
}
212+
}
213+
})
214+
);
215+
mapDiv.addEventListener(
216+
'touchcancel',
217+
(this.touchCancelHandler = () => {
218+
google.maps.event.trigger(this.container, 'touchend');
219+
})
220+
);
221+
}
222+
223+
removeEvent() {
224+
this.mounseDownHandler && this.container.removeEventListener('mousedown', this.mounseDownHandler);
225+
this.mouseMoveHandler && window.removeEventListener('mousemove', this.mouseMoveHandler);
226+
this.mouseUpHandler && window.removeEventListener('mouseup', this.mouseUpHandler);
227+
this.mouseLeaveHandler && this.map.getDiv().removeEventListener('mouseleave', this.mouseLeaveHandler);
228+
this.touchStartHandler && this.container.removeEventListener('touchstart', this.touchStartHandler);
229+
this.touchMoveHandler && window.removeEventListener('touchmove', this.touchMoveHandler);
230+
this.touchEndHandler && window.removeEventListener('touchend', this.touchEndHandler);
231+
this.touchCancelHandler && this.map.getDiv().removeEventListener('touchcancel', this.touchCancelHandler);
232+
}
233+
234+
addDragEventListener(key: EventType, fn: (position: google.maps.LatLngLiteral) => void) {
235+
this.eventMap.set(key, fn);
236+
237+
return () => this.eventMap.delete(key);
238+
}
239+
240+
updatePosition(position: google.maps.LatLngLiteral) {
241+
this.position = position;
242+
this.draw();
243+
}
244+
}
245+
246+
return classCache;
247+
}

0 commit comments

Comments
 (0)