Skip to content

Commit 17e3570

Browse files
committed
增加中键拖动旋转地图
1 parent 6d55143 commit 17e3570

File tree

3 files changed

+304
-2
lines changed

3 files changed

+304
-2
lines changed

packages/maplugin-core/types.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
export type TMapEvent = 'click' | 'dblclick' | 'mousemove' | 'contextmenu' | 'zoom' | 'style.load';
22

33
export interface IMap {
4+
dragRotate: number,
5+
getContainer(): HTMLElement,
6+
47
getStyle(): any;
58
setStyle(style: string): any;
69
setCenter(center: [number, number]): void,
7-
10+
setBearing(bearing: number): void;
11+
getBearing(): number;
12+
setPitch(pitch: number): void;
13+
getPitch(): number;
814
getZoom(): number,
915
setZoom(zoom: number): void,
1016

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './measurement';
22
export * from './tools';
3-
export * from './units';
3+
export * from './units';
4+
export * from './middle-button-roate';
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
import { IMap } from '../types'
2+
3+
type MercatorCoordinate = {
4+
x: number,
5+
y: number,
6+
z: number
7+
}
8+
9+
class Point {
10+
/**
11+
*
12+
*/
13+
constructor(public x: number, public y: number) {
14+
15+
}
16+
17+
dist(point: Point) {
18+
return Math.sqrt(Math.pow((this.x - point.x), 2) + Math.pow((this.y - point.y), 2));
19+
}
20+
21+
sub(point: Point) {
22+
return new Point(this.x - point.x, this.y - point.y);
23+
}
24+
}
25+
26+
type HandlerResult = {
27+
panDelta?: Point,
28+
zoomDelta?: number,
29+
bearingDelta?: number,
30+
pitchDelta?: number,
31+
// the point to not move when changing the camera
32+
around?: Point | null,
33+
// same as above, except for pinch actions, which are given higher priority
34+
pinchAround?: Point | null,
35+
// the point to not move when changing the camera in mercator coordinates
36+
aroundCoord?: MercatorCoordinate | null,
37+
// A method that can fire a one-off easing by directly changing the map's camera.
38+
cameraAnimation?: (map: IMap) => any;
39+
40+
// The last three properties are needed by only one handler: scrollzoom.
41+
// The DOM event to be used as the `originalEvent` on any camera change events.
42+
originalEvent?: any,
43+
// Makes the manager trigger a frame, allowing the handler to return multiple results over time (see scrollzoom).
44+
needsRenderFrame?: boolean,
45+
// The camera changes won't get recorded for inertial zooming.
46+
noInertia?: boolean
47+
};
48+
49+
namespace DOM {
50+
export function mouseButton(e: MouseEvent): number {
51+
if (e.type === 'mousedown' || e.type === 'mouseup') {
52+
if (typeof (window as any).InstallTrigger !== 'undefined' && e.button === 2 && e.ctrlKey &&
53+
window.navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
54+
// Fix for https://github.com/mapbox/mapbox-gl-js/issues/3131:
55+
// Firefox (detected by InstallTrigger) on Mac determines e.button = 2 when
56+
// using Control + left click
57+
return 0;
58+
}
59+
60+
return e.button as any;
61+
}
62+
63+
return -1;
64+
}
65+
66+
// Suppress the next click, but only if it's immediate.
67+
function suppressClickListener(e: Event) {
68+
e.preventDefault();
69+
e.stopPropagation();
70+
window.removeEventListener('click', suppressClickListener, true);
71+
}
72+
73+
export function suppressClick() {
74+
window.addEventListener('click', suppressClickListener, true);
75+
window.setTimeout(() => {
76+
window.removeEventListener('click', suppressClickListener, true);
77+
}, 0);
78+
}
79+
80+
const docStyle = window.document && window.document.documentElement.style;
81+
const selectProp = docStyle && docStyle.userSelect !== undefined ? 'userSelect' : 'WebkitUserSelect';
82+
let userSelect: any;
83+
84+
export function disableDrag() {
85+
if (docStyle && selectProp) {
86+
userSelect = docStyle[selectProp as any];
87+
docStyle[selectProp as any] = 'none';
88+
}
89+
}
90+
91+
export function enableDrag() {
92+
if (docStyle && selectProp) {
93+
docStyle[selectProp as any] = userSelect;
94+
}
95+
}
96+
97+
export function mousePos(el: HTMLElement, e: MouseEvent | WheelEvent): Point {
98+
const rect = el.getBoundingClientRect();
99+
return getScaledPoint(el, rect, e);
100+
}
101+
102+
function getScaledPoint(el: HTMLElement, rect: ClientRect, e: MouseEvent | WheelEvent | Touch) {
103+
// Until we get support for pointer events (https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent)
104+
// we use this dirty trick which would not work for the case of rotated transforms, but works well for
105+
// the case of simple scaling.
106+
// Note: `el.offsetWidth === rect.width` eliminates the `0/0` case.
107+
const scaling = el.offsetWidth === rect.width ? 1 : el.offsetWidth / rect.width;
108+
return new Point(
109+
(e.clientX - rect.left) * scaling,
110+
(e.clientY - rect.top) * scaling
111+
);
112+
}
113+
}
114+
115+
abstract class MouseHandler {
116+
117+
_enabled: boolean = true;
118+
_active?: boolean;
119+
_lastPoint?: Point;
120+
_eventButton?: number;
121+
_moved?: boolean;
122+
_clickTolerance: number;
123+
124+
constructor(options: { clickTolerance?: number }) {
125+
this.reset();
126+
this._clickTolerance = options.clickTolerance || 1;
127+
}
128+
129+
blur() {
130+
this.reset();
131+
}
132+
133+
reset() {
134+
this._active = false;
135+
this._moved = false;
136+
this._lastPoint = undefined;
137+
this._eventButton = undefined;
138+
}
139+
140+
abstract correctButton(e: MouseEvent, button: number): boolean;
141+
142+
abstract move(lastPoint: Point, point: Point): HandlerResult;
143+
144+
mousedown(e: MouseEvent, point: Point) {
145+
if (this._lastPoint) return;
146+
147+
const eventButton = DOM.mouseButton(e);
148+
if (!this.correctButton(e, eventButton)) return;
149+
150+
this._lastPoint = point;
151+
this._eventButton = eventButton;
152+
}
153+
154+
mousemoveWindow(e: MouseEvent, point: Point): HandlerResult | undefined {
155+
const lastPoint = this._lastPoint;
156+
if (!lastPoint) return;
157+
e.preventDefault();
158+
159+
if (!this._moved && point.dist(lastPoint) < this._clickTolerance) return;
160+
this._moved = true;
161+
this._lastPoint = point;
162+
163+
// implemented by child class
164+
return this.move(lastPoint, point);
165+
}
166+
167+
mouseupWindow(e: MouseEvent) {
168+
if (!this._lastPoint) return;
169+
const eventButton = DOM.mouseButton(e);
170+
if (eventButton !== this._eventButton) return;
171+
if (this._moved) DOM.suppressClick();
172+
this.reset();
173+
}
174+
175+
enable() {
176+
this._enabled = true;
177+
}
178+
179+
disable() {
180+
this._enabled = false;
181+
this.reset();
182+
}
183+
184+
isEnabled(): boolean {
185+
return this._enabled;
186+
}
187+
188+
isActive(): boolean {
189+
return this._active === true;
190+
}
191+
}
192+
193+
class MouseRotateHandler extends MouseHandler {
194+
correctButton(e: MouseEvent, button: number): boolean {
195+
return button === 1;
196+
}
197+
198+
move(lastPoint: Point, point: Point): HandlerResult {
199+
const degreesPerPixelMoved = 0.8;
200+
const bearingDelta = (point.x - lastPoint.x) * degreesPerPixelMoved;
201+
this._active = true;
202+
return { bearingDelta };
203+
}
204+
205+
contextmenu(e: MouseEvent) {
206+
// prevent browser context menu when necessary; we don't allow it with rotation
207+
// because we can't discern rotation gesture start from contextmenu on Mac
208+
e.preventDefault();
209+
}
210+
}
211+
212+
class MousePitchHandler extends MouseHandler {
213+
correctButton(e: MouseEvent, button: number): boolean {
214+
return button === 1;
215+
}
216+
217+
move(lastPoint: Point, point: Point): HandlerResult {
218+
const degreesPerPixelMoved = -0.5;
219+
const pitchDelta = (point.y - lastPoint.y) * degreesPerPixelMoved;
220+
this._active = true;
221+
return { pitchDelta };
222+
}
223+
224+
contextmenu(e: MouseEvent) {
225+
// prevent browser context menu when necessary; we don't allow it with rotation
226+
// because we can't discern rotation gesture start from contextmenu on Mac
227+
e.preventDefault();
228+
}
229+
}
230+
231+
232+
export class MiddleButtonRoate {
233+
private mouseRotate: MouseRotateHandler;
234+
private mousePitch?: MousePitchHandler;
235+
enable = true;
236+
237+
/**
238+
*
239+
*/
240+
constructor(private map: IMap, options: {
241+
withPitch?: boolean
242+
} = {}) {
243+
options.withPitch ??= true;
244+
245+
const element = map.getContainer();
246+
this.map = map;
247+
this.mouseRotate = new MouseRotateHandler({ clickTolerance: (map.dragRotate as any)._mouseRotate._clickTolerance });
248+
if (options.withPitch)
249+
this.mousePitch = new MousePitchHandler({ clickTolerance: (map.dragRotate as any)._mousePitch._clickTolerance });
250+
251+
const mousemove = (e: MouseEvent) => {
252+
this.move(e, DOM.mousePos(element, e));
253+
}
254+
255+
const mouseup = (e: MouseEvent) => {
256+
this.mouseRotate.mouseupWindow(e);
257+
this.mousePitch?.mouseupWindow(e);
258+
offTemp();
259+
}
260+
261+
const offTemp = () => {
262+
DOM.enableDrag();
263+
window.removeEventListener('mousemove', mousemove);
264+
window.removeEventListener('mouseup', mouseup);
265+
}
266+
267+
const mousedown = (e: MouseEvent) => {
268+
if (!this.enable) return;
269+
270+
this.down(e, DOM.mousePos(element, e));
271+
window.addEventListener('mousemove', mousemove);
272+
window.addEventListener('mouseup', mouseup);
273+
}
274+
275+
element.addEventListener('mousedown', mousedown);
276+
}
277+
278+
private down(e: MouseEvent, point: Point) {
279+
this.mouseRotate.mousedown(e, point);
280+
this.mousePitch?.mousedown(e, point);
281+
DOM.disableDrag();
282+
}
283+
284+
private move(e: MouseEvent, point: Point) {
285+
const map = this.map;
286+
const r = this.mouseRotate.mousemoveWindow(e, point);
287+
const delta = r && r.bearingDelta;
288+
if (delta) map.setBearing(map.getBearing() + delta);
289+
if (this.mousePitch) {
290+
const p = this.mousePitch.mousemoveWindow(e, point);
291+
const delta = p && p.pitchDelta;
292+
if (delta) map.setPitch(map.getPitch() + delta);
293+
}
294+
}
295+
}

0 commit comments

Comments
 (0)