mirror of
https://github.com/visgl/react-map-gl.git
synced 2026-01-25 16:02:50 +00:00
MapControls Refactor (#245)
This commit is contained in:
parent
4a6164daa0
commit
2eccb0328a
@ -5,6 +5,9 @@
|
||||
[TBD]
|
||||
- New event management system based on hammer.js
|
||||
- FIX: Touch interaction
|
||||
- Remove `MapControls` React component
|
||||
- Remove `ControllerClass` prop from `InteractiveMap`
|
||||
- Add `mapControls` prop to `InteractiveMap`
|
||||
|
||||
### Version 3.0.0-alpha.10 - Add `ControllerClass` prop to `InteractiveMap`
|
||||
|
||||
|
||||
52
docs/advanced/custom-map-controls.md
Normal file
52
docs/advanced/custom-map-controls.md
Normal file
@ -0,0 +1,52 @@
|
||||
# Custom Map Controls
|
||||
|
||||
## Overriding The Default Map Controller
|
||||
|
||||
To change the default behavior of map interaction, you can implement/extend the `MapControls`
|
||||
class add pass an instance to the `mapControls` prop of `InteractiveMap`.
|
||||
|
||||
A simple example to disable mouse wheel:
|
||||
```js
|
||||
/// my-map-controls.js
|
||||
import {experimental} from 'react-map-gl';
|
||||
|
||||
export default class MyMapControls extends experimental.MapControls {
|
||||
|
||||
// override the default handler in MapControls
|
||||
handle(event) {
|
||||
if (event.type === 'wheel') {
|
||||
return false;
|
||||
}
|
||||
return super.handle(event);
|
||||
}
|
||||
}
|
||||
```
|
||||
Then pass it to the map during render:
|
||||
```jsx
|
||||
<MapGL mapControls={new MyMapControls()} ... />
|
||||
```
|
||||
|
||||
|
||||
## MapControls Interface
|
||||
|
||||
A custom map controls class must implement the following interface:
|
||||
|
||||
### Properties
|
||||
|
||||
##### `events` (Array)
|
||||
|
||||
A list of event names that this control subscribes to.
|
||||
|
||||
Available events: `click`, `tap`, `doubletap`, `press`, `pinch`, `pinchin`, `pinchout`, `pinchstart`, `pinchmove`, `pinchend`, `pinchcancel`, `rotate`, `rotatestart`, `rotatemove`, `rotateend`, `rotatecancel`, `pan`, `panstart`, `panmove`, `panup`, `pandown`, `panleft`, `panright`, `panend`, `pancancel`, `swipe`, `swipeleft`, `swiperight`, `swipeup`, `swipedown`, `pointerdown`, `pointermove`, `pointerup`, `touchstart`, `touchmove`, `touchend`, `mousedown`, `mousemove`, and `mouseup`.
|
||||
|
||||
[Event object](http://hammerjs.github.io/api/#event-object) is generated by [hammer.js](http://hammerjs.github.io).
|
||||
|
||||
### Methods
|
||||
|
||||
##### `setState(state)`
|
||||
|
||||
Used by `InteractiveMap` to update this control's state.
|
||||
|
||||
##### `handle(event)`
|
||||
|
||||
Called by `InteractiveMap` to handle pointer events.
|
||||
@ -3,10 +3,13 @@ import PropTypes from 'prop-types';
|
||||
import autobind from '../utils/autobind';
|
||||
|
||||
import StaticMap from './static-map';
|
||||
import MapControls from './map-controls';
|
||||
import MapState, {MAPBOX_MAX_PITCH, MAPBOX_MAX_ZOOM} from '../utils/map-state';
|
||||
import {PerspectiveMercatorViewport} from 'viewport-mercator-project';
|
||||
|
||||
import EventManager from '../utils/event-manager/event-manager';
|
||||
import MapControls from '../utils/map-controls';
|
||||
import config from '../config';
|
||||
|
||||
const propTypes = Object.assign({}, StaticMap.propTypes, {
|
||||
// Additional props on top of StaticMap
|
||||
|
||||
@ -38,15 +41,33 @@ const propTypes = Object.assign({}, StaticMap.propTypes, {
|
||||
*/
|
||||
onChangeViewport: PropTypes.func,
|
||||
|
||||
/** Enables perspective control event handling */
|
||||
perspectiveEnabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Is the component currently being dragged. This is used to show/hide the
|
||||
* drag cursor. Also used as an optimization in some overlays by preventing
|
||||
* rendering while dragging.
|
||||
*/
|
||||
isHovering: PropTypes.bool,
|
||||
isDragging: PropTypes.bool,
|
||||
|
||||
/** Advanced features */
|
||||
// Contraints for displaying the map. If not met, then the map is hidden.
|
||||
displayConstraints: PropTypes.object.isRequired,
|
||||
// A React component class definition to replace the default map controls
|
||||
ControllerClass: PropTypes.func
|
||||
// A map control instance to replace the default map controls
|
||||
// The object must expose one property: `events` as an array of subscribed
|
||||
// event names; and two methods: `setState(state)` and `handle(event)`
|
||||
mapControls: PropTypes.shape({
|
||||
events: PropTypes.arrayOf(PropTypes.string),
|
||||
setState: PropTypes.func,
|
||||
handle: PropTypes.func
|
||||
})
|
||||
});
|
||||
|
||||
const defaultProps = Object.assign({}, StaticMap.defaultProps, {
|
||||
onChangeViewport: null,
|
||||
perspectiveEnabled: false,
|
||||
|
||||
/** Viewport constraints */
|
||||
maxZoom: MAPBOX_MAX_ZOOM,
|
||||
@ -58,7 +79,8 @@ const defaultProps = Object.assign({}, StaticMap.defaultProps, {
|
||||
maxZoom: MAPBOX_MAX_ZOOM,
|
||||
maxPitch: MAPBOX_MAX_PITCH
|
||||
},
|
||||
ControllerClass: MapControls
|
||||
|
||||
mapControls: new MapControls()
|
||||
});
|
||||
|
||||
export default class InteractiveMap extends PureComponent {
|
||||
@ -78,11 +100,58 @@ export default class InteractiveMap extends PureComponent {
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Register event handlers
|
||||
const {eventCanvas} = this.refs;
|
||||
const {mapControls} = this.props;
|
||||
|
||||
this._eventManager = new EventManager(eventCanvas);
|
||||
|
||||
mapControls.events.forEach(event => this._eventManager.on(event, this._handleEvent));
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._eventManager) {
|
||||
// Must destroy because hammer adds event listeners to window
|
||||
this._eventManager.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
_handleEvent(event) {
|
||||
const {mapControls} = this.props;
|
||||
// MapControls only extracts the states that it recognizes.
|
||||
// This allows custom map controls to add new states and callbacks.
|
||||
mapControls.setState(Object.assign({}, this.props, {
|
||||
mapState: new MapState(this.props)
|
||||
}));
|
||||
|
||||
return mapControls.handle(event);
|
||||
}
|
||||
|
||||
// TODO - Remove once Viewport alternative is good enough
|
||||
_getMap() {
|
||||
return this._map._getMap();
|
||||
}
|
||||
|
||||
// Calculate a cursor style to show that we are in "dragging state"
|
||||
_getCursor() {
|
||||
const isInteractive =
|
||||
this.props.onChangeViewport ||
|
||||
this.props.onClickFeature ||
|
||||
this.props.onHoverFeatures;
|
||||
|
||||
if (!isInteractive) {
|
||||
return 'inherit';
|
||||
}
|
||||
if (this.props.isDragging) {
|
||||
return config.CURSOR.GRABBING;
|
||||
}
|
||||
if (this.props.isHovering) {
|
||||
return config.CURSOR.POINTER;
|
||||
}
|
||||
return config.CURSOR.GRAB;
|
||||
}
|
||||
|
||||
// Checks a displayConstraints object to see if the map should be displayed
|
||||
checkDisplayConstraints(props) {
|
||||
const capitalize = s => s[0].toUpperCase() + s.slice(1);
|
||||
@ -104,7 +173,7 @@ export default class InteractiveMap extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const {width, height, ControllerClass} = this.props;
|
||||
const {width, height} = this.props;
|
||||
const mapVisible = this.checkDisplayConstraints(this.props);
|
||||
const visibility = mapVisible ? 'visible' : 'hidden';
|
||||
const overlayContainerStyle = {
|
||||
@ -116,12 +185,19 @@ export default class InteractiveMap extends PureComponent {
|
||||
overflow: 'hidden'
|
||||
};
|
||||
|
||||
const eventCanvasStyle = {
|
||||
width,
|
||||
height,
|
||||
position: 'relative',
|
||||
cursor: this._getCursor()
|
||||
};
|
||||
|
||||
return (
|
||||
createElement(ControllerClass, Object.assign({}, this.props, {
|
||||
createElement('div', {
|
||||
key: 'map-controls',
|
||||
style: {position: 'relative'},
|
||||
mapState: new MapState(this.props)
|
||||
}), [
|
||||
ref: 'eventCanvas',
|
||||
style: eventCanvasStyle
|
||||
}, [
|
||||
createElement(StaticMap, Object.assign({}, this.props, {
|
||||
key: 'map-static',
|
||||
style: {visibility},
|
||||
|
||||
@ -1,256 +0,0 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
import React, {PureComponent} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import autobind from '../utils/autobind';
|
||||
|
||||
import MapState from '../utils/map-state';
|
||||
import config from '../config';
|
||||
|
||||
import EventManager from '../utils/event-manager/event-manager';
|
||||
|
||||
// EVENT HANDLING PARAMETERS
|
||||
const PITCH_MOUSE_THRESHOLD = 5;
|
||||
const PITCH_ACCEL = 1.2;
|
||||
const ZOOM_ACCEL = 0.01;
|
||||
|
||||
const propTypes = {
|
||||
mapState: PropTypes.instanceOf(MapState).isRequired,
|
||||
|
||||
/** Enables perspective control event handling */
|
||||
perspectiveEnabled: PropTypes.bool,
|
||||
/**
|
||||
* `onChangeViewport` callback is fired when the user interacted with the
|
||||
* map. The object passed to the callback contains `latitude`,
|
||||
* `longitude` and `zoom` and additional state information.
|
||||
*/
|
||||
onChangeViewport: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Is the component currently being dragged. This is used to show/hide the
|
||||
* drag cursor. Also used as an optimization in some overlays by preventing
|
||||
* rendering while dragging.
|
||||
*/
|
||||
isHovering: PropTypes.bool,
|
||||
isDragging: PropTypes.bool
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
perspectiveEnabled: false,
|
||||
onChangeViewport: null
|
||||
};
|
||||
|
||||
export default class MapControls extends PureComponent {
|
||||
/**
|
||||
* @classdesc
|
||||
* A component that monitors events and updates mercator style viewport parameters
|
||||
* It can be used with our without a mapbox map
|
||||
* (e.g. it could pan over a static map image)
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
autobind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Register event handlers
|
||||
const {canvas} = this.refs;
|
||||
|
||||
this._eventManager = new EventManager(canvas)
|
||||
.on({
|
||||
panstart: this._onPanStart,
|
||||
pan: this._onPan,
|
||||
panend: this._onPanEnd,
|
||||
pinchstart: this._onPinchStart,
|
||||
pinch: this._onPinch,
|
||||
pinchend: this._onPinchEnd,
|
||||
doubletap: this._onDoubleTap,
|
||||
wheel: this._onWheel
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._eventManager) {
|
||||
// Must destroy because hammer adds event listeners to window
|
||||
this._eventManager.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/* Event utils */
|
||||
// Event object: http://hammerjs.github.io/api/#event-object
|
||||
_getCenter(event) {
|
||||
const {center, target} = event;
|
||||
const rect = target.getBoundingClientRect();
|
||||
return [
|
||||
center.x - rect.left - target.clientLeft,
|
||||
center.y - rect.top - target.clientTop
|
||||
];
|
||||
}
|
||||
|
||||
_isFunctionKeyPressed(event) {
|
||||
const {srcEvent} = event;
|
||||
return Boolean(srcEvent.metaKey || srcEvent.altKey ||
|
||||
srcEvent.ctrlKey || srcEvent.shiftKey);
|
||||
}
|
||||
|
||||
// Calculate a cursor style to show that we are in "dragging state"
|
||||
_getCursor() {
|
||||
const isInteractive =
|
||||
this.props.onChangeViewport ||
|
||||
this.props.onClickFeature ||
|
||||
this.props.onHoverFeatures;
|
||||
|
||||
if (!isInteractive) {
|
||||
return 'inherit';
|
||||
}
|
||||
if (this.props.isDragging) {
|
||||
return config.CURSOR.GRABBING;
|
||||
}
|
||||
if (this.props.isHovering) {
|
||||
return config.CURSOR.POINTER;
|
||||
}
|
||||
return config.CURSOR.GRAB;
|
||||
}
|
||||
|
||||
_updateViewport(mapState, extraState = {}) {
|
||||
if (!this.props.onChangeViewport) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {isDragging} = this.props;
|
||||
return this.props.onChangeViewport(Object.assign(
|
||||
{isDragging},
|
||||
mapState.getViewportProps(),
|
||||
extraState
|
||||
));
|
||||
}
|
||||
|
||||
_onPanStart(event) {
|
||||
const pos = this._getCenter(event);
|
||||
const newMapState = this.props.mapState.panStart({pos}).rotateStart({pos});
|
||||
this._updateViewport(newMapState, {isDragging: true});
|
||||
}
|
||||
|
||||
_onPan(event) {
|
||||
return this._isFunctionKeyPressed(event) ? this._onRotateMap(event) : this._onPanMap(event);
|
||||
}
|
||||
|
||||
_onPanEnd(event) {
|
||||
const newMapState = this.props.mapState.panEnd().rotateEnd();
|
||||
this._updateViewport(newMapState, {isDragging: false});
|
||||
}
|
||||
|
||||
_onPanMap(event) {
|
||||
const pos = this._getCenter(event);
|
||||
const newMapState = this.props.mapState.pan({pos});
|
||||
this._updateViewport(newMapState);
|
||||
}
|
||||
|
||||
_onRotateMap(event) {
|
||||
if (!this.props.perspectiveEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {deltaX, deltaY} = event;
|
||||
const [, centerY] = this._getCenter(event);
|
||||
const startY = centerY - deltaY;
|
||||
|
||||
const deltaScaleX = deltaX / this.props.width;
|
||||
let deltaScaleY = 0;
|
||||
|
||||
if (deltaY > 0) {
|
||||
if (Math.abs(this.props.height - startY) > PITCH_MOUSE_THRESHOLD) {
|
||||
// Move from 0 to -1 as we drag upwards
|
||||
deltaScaleY = deltaY / (startY - this.props.height) * PITCH_ACCEL;
|
||||
}
|
||||
} else if (deltaY < 0) {
|
||||
if (startY > PITCH_MOUSE_THRESHOLD) {
|
||||
// Move from 0 to 1 as we drag upwards
|
||||
deltaScaleY = 1 - centerY / startY;
|
||||
}
|
||||
}
|
||||
deltaScaleY = Math.min(1, Math.max(-1, deltaScaleY));
|
||||
|
||||
const newMapState = this.props.mapState.rotate({deltaScaleX, deltaScaleY});
|
||||
this._updateViewport(newMapState);
|
||||
}
|
||||
|
||||
_onWheel(event) {
|
||||
const pos = this._getCenter(event);
|
||||
const {delta} = event;
|
||||
|
||||
// Map wheel delta to relative scale
|
||||
let scale = 2 / (1 + Math.exp(-Math.abs(delta * ZOOM_ACCEL)));
|
||||
if (delta < 0 && scale !== 0) {
|
||||
scale = 1 / scale;
|
||||
}
|
||||
|
||||
const newMapState = this.props.mapState.zoom({pos, scale});
|
||||
this._updateViewport(newMapState);
|
||||
}
|
||||
|
||||
_onPinchStart(event) {
|
||||
const pos = this._getCenter(event);
|
||||
const newMapState = this.props.mapState.zoomStart({pos});
|
||||
this._updateViewport(newMapState, {isDragging: true});
|
||||
}
|
||||
|
||||
_onPinch(event) {
|
||||
const pos = this._getCenter(event);
|
||||
const {scale} = event;
|
||||
const newMapState = this.props.mapState.zoom({pos, scale});
|
||||
this._updateViewport(newMapState);
|
||||
}
|
||||
|
||||
_onPinchEnd(event) {
|
||||
const newMapState = this.props.mapState.zoomEnd();
|
||||
this._updateViewport(newMapState, {isDragging: false});
|
||||
}
|
||||
|
||||
_onDoubleTap(event) {
|
||||
const pos = this._getCenter(event);
|
||||
const isZoomOut = this._isFunctionKeyPressed(event);
|
||||
|
||||
const newMapState = this.props.mapState.zoom({pos, scale: isZoomOut ? 0.5 : 2});
|
||||
this._updateViewport(newMapState);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {className, width, height, style} = this.props;
|
||||
|
||||
const mapEventLayerStyle = Object.assign({}, style, {
|
||||
width,
|
||||
height,
|
||||
position: 'relative',
|
||||
cursor: this._getCursor()
|
||||
});
|
||||
|
||||
return React.createElement('div', {
|
||||
ref: 'canvas',
|
||||
style: mapEventLayerStyle,
|
||||
className
|
||||
}, this.props.children);
|
||||
}
|
||||
}
|
||||
|
||||
MapControls.displayName = 'MapControls';
|
||||
MapControls.propTypes = propTypes;
|
||||
MapControls.defaultProps = defaultProps;
|
||||
@ -21,7 +21,6 @@
|
||||
// React Components
|
||||
export {default as InteractiveMap} from './components/interactive-map';
|
||||
export {default as StaticMap} from './components/static-map';
|
||||
export {default as MapControls} from './components/map-controls';
|
||||
|
||||
export {default as default} from './components/interactive-map';
|
||||
export {default as MapGL} from './components/interactive-map';
|
||||
@ -49,3 +48,10 @@ export {default as DraggablePointsOverlay} from './overlays/draggable-points-ove
|
||||
export {default as HTMLOverlay} from './overlays/html-overlay';
|
||||
export {default as ScatterplotOverlay} from './overlays/scatterplot-overlay';
|
||||
export {default as SVGOverlay} from './overlays/svg-overlay';
|
||||
|
||||
// Experimental Features (May change in minor version bumps, use at your own risk)
|
||||
import MapControls from './utils/map-controls';
|
||||
|
||||
export const experimental = {
|
||||
MapControls
|
||||
};
|
||||
|
||||
219
src/utils/map-controls.js
Normal file
219
src/utils/map-controls.js
Normal file
@ -0,0 +1,219 @@
|
||||
// Copyright (c) 2015 Uber Technologies, Inc.
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// EVENT HANDLING PARAMETERS
|
||||
const PITCH_MOUSE_THRESHOLD = 5;
|
||||
const PITCH_ACCEL = 1.2;
|
||||
const ZOOM_ACCEL = 0.01;
|
||||
|
||||
const SUBSCRIBED_EVENTS = [
|
||||
'panstart',
|
||||
'pan',
|
||||
'panend',
|
||||
'pinchstart',
|
||||
'pinch',
|
||||
'pinchend',
|
||||
'doubletap',
|
||||
'wheel'
|
||||
];
|
||||
|
||||
export default class MapControls {
|
||||
/**
|
||||
* @classdesc
|
||||
* A class that handles events and updates mercator style viewport parameters
|
||||
*/
|
||||
constructor() {
|
||||
this.events = SUBSCRIBED_EVENTS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of the control
|
||||
* @param {MapState} state.mapState - current map state
|
||||
* @param {Function} state.onChangeViewport - callback
|
||||
*/
|
||||
setState({mapState, onChangeViewport, isDragging, perspectiveEnabled}) {
|
||||
this.mapState = mapState;
|
||||
this.onChangeViewport = onChangeViewport;
|
||||
this.isDragging = isDragging;
|
||||
this.perspectiveEnabled = perspectiveEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for events
|
||||
* @param {hammer.Event} event
|
||||
*/
|
||||
handle(event) {
|
||||
switch (event.type) {
|
||||
case 'panstart':
|
||||
return this._onPanStart(event);
|
||||
case 'pan':
|
||||
return this._onPan(event);
|
||||
case 'panend':
|
||||
return this._onPanEnd(event);
|
||||
case 'pinchstart':
|
||||
return this._onPinchStart(event);
|
||||
case 'pinch':
|
||||
return this._onPinch(event);
|
||||
case 'pinchend':
|
||||
return this._onPinchEnd(event);
|
||||
case 'doubletap':
|
||||
return this._onDoubleTap(event);
|
||||
case 'wheel':
|
||||
return this._onWheel(event);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Event utils */
|
||||
// Event object: http://hammerjs.github.io/api/#event-object
|
||||
getCenter(event) {
|
||||
const {center, target} = event;
|
||||
const rect = target.getBoundingClientRect();
|
||||
return [
|
||||
center.x - rect.left - target.clientLeft,
|
||||
center.y - rect.top - target.clientTop
|
||||
];
|
||||
}
|
||||
|
||||
isFunctionKeyPressed(event) {
|
||||
const {srcEvent} = event;
|
||||
return Boolean(srcEvent.metaKey || srcEvent.altKey ||
|
||||
srcEvent.ctrlKey || srcEvent.shiftKey);
|
||||
}
|
||||
|
||||
/* Callback util */
|
||||
// formats map state and invokes callback function
|
||||
updateViewport(mapState, extraState = {}) {
|
||||
if (!this.onChangeViewport) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.onChangeViewport(Object.assign(
|
||||
{isDragging: this.isDragging},
|
||||
mapState.getViewportProps(),
|
||||
extraState
|
||||
));
|
||||
}
|
||||
|
||||
/* Event handlers */
|
||||
// Default handler for the `panstart` event.
|
||||
_onPanStart(event) {
|
||||
const pos = this.getCenter(event);
|
||||
const newMapState = this.mapState.panStart({pos}).rotateStart({pos});
|
||||
return this.updateViewport(newMapState, {isDragging: true});
|
||||
}
|
||||
|
||||
// Default handler for the `pan` event.
|
||||
_onPan(event) {
|
||||
return this.isFunctionKeyPressed(event) ? this._onPanRotate(event) : this._onPanMove(event);
|
||||
}
|
||||
|
||||
// Default handler for the `panend` event.
|
||||
_onPanEnd(event) {
|
||||
const newMapState = this.mapState.panEnd().rotateEnd();
|
||||
return this.updateViewport(newMapState, {isDragging: false});
|
||||
}
|
||||
|
||||
// Default handler for panning to move.
|
||||
// Called by `_onPan` when panning without function key pressed.
|
||||
_onPanMove(event) {
|
||||
const pos = this.getCenter(event);
|
||||
const newMapState = this.mapState.pan({pos});
|
||||
return this.updateViewport(newMapState);
|
||||
}
|
||||
|
||||
// Default handler for panning to rotate.
|
||||
// Called by `_onPan` when panning with function key pressed.
|
||||
_onPanRotate(event) {
|
||||
if (!this.perspectiveEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const {deltaX, deltaY} = event;
|
||||
const [, centerY] = this.getCenter(event);
|
||||
const startY = centerY - deltaY;
|
||||
const {width, height} = this.mapState.getViewportProps();
|
||||
|
||||
const deltaScaleX = deltaX / width;
|
||||
let deltaScaleY = 0;
|
||||
|
||||
if (deltaY > 0) {
|
||||
if (Math.abs(height - startY) > PITCH_MOUSE_THRESHOLD) {
|
||||
// Move from 0 to -1 as we drag upwards
|
||||
deltaScaleY = deltaY / (startY - height) * PITCH_ACCEL;
|
||||
}
|
||||
} else if (deltaY < 0) {
|
||||
if (startY > PITCH_MOUSE_THRESHOLD) {
|
||||
// Move from 0 to 1 as we drag upwards
|
||||
deltaScaleY = 1 - centerY / startY;
|
||||
}
|
||||
}
|
||||
deltaScaleY = Math.min(1, Math.max(-1, deltaScaleY));
|
||||
|
||||
const newMapState = this.mapState.rotate({deltaScaleX, deltaScaleY});
|
||||
return this.updateViewport(newMapState);
|
||||
}
|
||||
|
||||
// Default handler for the `wheel` event.
|
||||
_onWheel(event) {
|
||||
const pos = this.getCenter(event);
|
||||
const {delta} = event;
|
||||
|
||||
// Map wheel delta to relative scale
|
||||
let scale = 2 / (1 + Math.exp(-Math.abs(delta * ZOOM_ACCEL)));
|
||||
if (delta < 0 && scale !== 0) {
|
||||
scale = 1 / scale;
|
||||
}
|
||||
|
||||
const newMapState = this.mapState.zoom({pos, scale});
|
||||
return this.updateViewport(newMapState);
|
||||
}
|
||||
|
||||
// Default handler for the `pinchstart` event.
|
||||
_onPinchStart(event) {
|
||||
const pos = this.getCenter(event);
|
||||
const newMapState = this.mapState.zoomStart({pos});
|
||||
return this.updateViewport(newMapState, {isDragging: true});
|
||||
}
|
||||
|
||||
// Default handler for the `pinch` event.
|
||||
_onPinch(event) {
|
||||
const pos = this.getCenter(event);
|
||||
const {scale} = event;
|
||||
const newMapState = this.mapState.zoom({pos, scale});
|
||||
return this.updateViewport(newMapState);
|
||||
}
|
||||
|
||||
// Default handler for the `pinchend` event.
|
||||
_onPinchEnd(event) {
|
||||
const newMapState = this.mapState.zoomEnd();
|
||||
return this.updateViewport(newMapState, {isDragging: false});
|
||||
}
|
||||
|
||||
// Default handler for the `doubletap` event.
|
||||
_onDoubleTap(event) {
|
||||
const pos = this.getCenter(event);
|
||||
const isZoomOut = this.isFunctionKeyPressed(event);
|
||||
|
||||
const newMapState = this.mapState.zoom({pos, scale: isZoomOut ? 0.5 : 2});
|
||||
return this.updateViewport(newMapState);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user