s2maps-gpu
Version:
S2 Maps GPU - An open source, high-performance, and GPU-accelerated map engine for rendering large-scale, interactive maps.
176 lines (175 loc) • 6.1 kB
JavaScript
const DOUBLE_CLICK_TIMEOUT = 175;
/**
* # DragPan
*
* ## Description
* DragPan is a class that handles mouse and touch events for panning and zooming
*
* ## Example
* ```ts
* const dragPan = new DragPan()
* dragPan.addEventListener('move', () => {
* console.info(dragPan.movementX, dragPan.movementY)
* })
* ```
*/
export default class DragPan extends EventTarget {
zoomActive = false; // allow two finger zooming
mouseActive = false; // when a user presses left click and moves during the press
minMovementX = 1;
minMovementY = 0.5;
movementX = 0;
movementY = 0;
totalMovementX = 0;
totalMovementY = 0;
touchDeltaX = 0;
touchDeltaY = 0;
touchDeltaZ = 0;
clickEvent = null;
zoom = 0;
/** Clears all previous movement and zoom changes */
clear() {
this.movementX = 0;
this.movementY = 0;
this.totalMovementX = 0;
this.totalMovementY = 0;
this.zoom = 0;
}
/**
* Beginning of a touch event, user has just touched the screen
* @param touches - collection of touch events
*/
onTouchStart(touches) {
this.#setTouchDelta(touches);
this.mouseActive = true;
}
/**
* User has let go, we don't know if it was a swipe, a click, or a double click
* @param touches - collection of touch events
*/
onTouchEnd(touches) {
this.#setTouchDelta(touches);
if (Math.abs(this.totalMovementX) < this.minMovementX &&
Math.abs(this.totalMovementY) < this.minMovementY) {
if (this.clickEvent !== null) {
clearTimeout(this.clickEvent);
this.clickEvent = null;
this.dispatchEvent(new Event('doubleClick'));
}
else {
this.clickEvent = setTimeout(() => {
this.clickEvent = null;
this.dispatchEvent(new Event('click'));
}, DOUBLE_CLICK_TIMEOUT);
}
}
}
/**
* User is actively moving their fingers
* @param touches - collection of touch events
*/
#setTouchDelta(touches) {
const { length } = touches;
if (length !== 0) {
let { clientX, clientY } = touches[0];
this.touchDeltaX = clientX;
this.touchDeltaY = clientY;
if (length > 1 && this.zoomActive) {
clientX = touches[length - 1].clientX;
clientY = touches[length - 1].clientY;
this.touchDeltaZ = Math.hypot(this.touchDeltaX - clientX, this.touchDeltaY - clientY);
}
else {
this.clear();
}
}
else {
this.onMouseUp();
}
}
/** User is using a mouse and just clicked */
onMouseDown() {
// set to active and set starting movement
this.mouseActive = true;
this.clear();
}
/**
* User has let go of the left mouse button
* @param posX - x position of the click
* @param posY - y position of the click
*/
onMouseUp(posX = 0, posY = 0) {
this.mouseActive = false;
// if movement is greater than mins, animate swipe,
// otherwise if total movement is less than mins it's considered a click
if (Math.abs(this.movementX) > this.minMovementX ||
Math.abs(this.movementY) > this.minMovementY) {
this.dispatchEvent(new Event('swipe'));
}
else if (Math.abs(this.totalMovementX) < this.minMovementX &&
Math.abs(this.totalMovementY) < this.minMovementY) {
const click = { detail: { posX, posY } };
if (this.clickEvent !== null) {
clearTimeout(this.clickEvent);
this.clickEvent = null;
this.dispatchEvent(new CustomEvent('doubleClick', click));
}
else {
this.clickEvent = setTimeout(() => {
this.clickEvent = null;
this.dispatchEvent(new CustomEvent('click', click));
}, DOUBLE_CLICK_TIMEOUT);
}
}
}
/**
* tracks movement if the left click actively pressed
* or it tracks what features are currently active
* @param movementX - the change in x position
* @param movementY - the change in y position
*/
onMouseMove(movementX, movementY) {
if (this.mouseActive) {
this.movementX = movementX;
this.movementY = movementY;
this.totalMovementX += movementX;
this.totalMovementY += movementY;
this.dispatchEvent(new Event('move'));
}
}
/**
* User is using a touch screen and is actively moving their finger(s)
* @param touches - collection of touch events
*/
onTouchMove(touches) {
const { length } = touches;
let { clientX, clientY } = touches[0];
if (length > 1 && this.zoomActive) {
// zoom
// update new position of first finger
this.touchDeltaX = clientX;
this.touchDeltaY = clientY;
// get position of last finger
clientX = touches[length - 1].clientX;
clientY = touches[length - 1].clientY;
// set change in finger distance
const deltaZ = Math.hypot(this.touchDeltaX - clientX, this.touchDeltaY - clientY);
this.zoom = (this.touchDeltaZ - deltaZ) * 2;
this.dispatchEvent(new Event('zoom'));
this.touchDeltaZ = deltaZ;
}
else if (this.mouseActive) {
// move
const deltaX = clientX - this.touchDeltaX;
const deltaY = clientY - this.touchDeltaY;
this.movementX = deltaX;
this.movementY = deltaY;
this.totalMovementX += deltaX;
this.totalMovementY += deltaY;
// set new position
this.touchDeltaX = clientX;
this.touchDeltaY = clientY;
this.dispatchEvent(new Event('move'));
}
}
}