mirror of
https://github.com/visgl/react-map-gl.git
synced 2026-01-25 16:02:50 +00:00
Per the react docs, this use case is discouraged and can be replaced with the ref itself. It also allows the removal of ReactDOM from the src require statements https://facebook.github.io/react/docs/top-level-api.html#reactdom.finddomnode
223 lines
7.0 KiB
JavaScript
223 lines
7.0 KiB
JavaScript
// 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.
|
|
'use strict';
|
|
|
|
var React = require('react');
|
|
var MapboxGL = require('mapbox-gl');
|
|
var Point = MapboxGL.Point;
|
|
var document = require('global/document');
|
|
var window = require('global/window');
|
|
var r = require('r-dom');
|
|
var noop = require('./noop');
|
|
|
|
var ua = typeof window.navigator !== 'undefined' ?
|
|
window.navigator.userAgent.toLowerCase() : '';
|
|
var firefox = ua.indexOf('firefox') !== -1;
|
|
|
|
function mousePos(el, event) {
|
|
var rect = el.getBoundingClientRect();
|
|
event = event.touches ? event.touches[0] : event;
|
|
return new Point(
|
|
event.clientX - rect.left - el.clientLeft,
|
|
event.clientY - rect.top - el.clientTop
|
|
);
|
|
}
|
|
|
|
/* eslint-disable max-len */
|
|
// Portions of the code below originally from:
|
|
// https://github.com/mapbox/mapbox-gl-js/blob/master/js/ui/handler/scroll_zoom.js
|
|
/* eslint-enable max-len */
|
|
|
|
var MapInteractions = React.createClass({
|
|
|
|
displayName: 'MapInteractions',
|
|
|
|
PropTypes: {
|
|
width: React.PropTypes.number.isRequired,
|
|
height: React.PropTypes.number.isRequired,
|
|
onMouseDown: React.PropTypes.func,
|
|
onMouseDrag: React.PropTypes.func,
|
|
onMouseUp: React.PropTypes.func,
|
|
onZoom: React.PropTypes.func,
|
|
onZoomEnd: React.PropTypes.func
|
|
},
|
|
|
|
getDefaultProps: function getDefaultProps() {
|
|
return {
|
|
onMouseDown: noop,
|
|
onMouseDrag: noop,
|
|
onMouseUp: noop,
|
|
onZoom: noop,
|
|
onZoomEnd: noop
|
|
};
|
|
},
|
|
|
|
getInitialState: function getInitialState() {
|
|
return {
|
|
startPos: null,
|
|
pos: null,
|
|
mouseWheelPos: null
|
|
};
|
|
},
|
|
|
|
_getMousePos: function _getMousePos(event) {
|
|
var el = this.refs.container;
|
|
return mousePos(el, event);
|
|
},
|
|
|
|
_onMouseDown: function _onMouseDown(event) {
|
|
var pos = this._getMousePos(event);
|
|
this.setState({startPos: pos, pos: pos});
|
|
this.props.onMouseDown({pos: pos});
|
|
document.addEventListener('mousemove', this._onMouseDrag, false);
|
|
document.addEventListener('mouseup', this._onMouseUp, false);
|
|
},
|
|
|
|
_onMouseDrag: function _onMouseDrag(event) {
|
|
var pos = this._getMousePos(event);
|
|
this.setState({pos: pos});
|
|
this.props.onMouseDrag({pos: pos});
|
|
},
|
|
|
|
_onMouseUp: function _onMouseUp(event) {
|
|
document.removeEventListener('mousemove', this._onMouseDrag, false);
|
|
document.removeEventListener('mouseup', this._onMouseUp, false);
|
|
var pos = this._getMousePos(event);
|
|
this.setState({pos: pos});
|
|
this.props.onMouseUp({pos: pos});
|
|
},
|
|
|
|
_onMouseMove: function _onMouseMove(event) {
|
|
var pos = this._getMousePos(event);
|
|
this.props.onMouseMove({pos: pos});
|
|
},
|
|
|
|
/* eslint-disable complexity, max-statements */
|
|
_onWheel: function _onWheel(event) {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
var value = event.deltaY;
|
|
// Firefox doubles the values on retina screens...
|
|
if (firefox && event.deltaMode === window.WheelEvent.DOM_DELTA_PIXEL) {
|
|
value /= window.devicePixelRatio;
|
|
}
|
|
if (event.deltaMode === window.WheelEvent.DOM_DELTA_LINE) {
|
|
value *= 40;
|
|
}
|
|
|
|
var type = this.state.mouseWheelType;
|
|
var timeout = this.state.mouseWheelTimeout;
|
|
var lastValue = this.state.mouseWheelLastValue;
|
|
var time = this.state.mouseWheelTime;
|
|
|
|
var now = (window.performance || Date).now();
|
|
var timeDelta = now - (time || 0);
|
|
|
|
var pos = this._getMousePos(event);
|
|
time = now;
|
|
|
|
if (value !== 0 && value % 4.000244140625 === 0) {
|
|
// This one is definitely a mouse wheel event.
|
|
type = 'wheel';
|
|
// Normalize this value to match trackpad.
|
|
value = Math.floor(value / 4);
|
|
} else if (value !== 0 && Math.abs(value) < 4) {
|
|
// This one is definitely a trackpad event because it is so small.
|
|
type = 'trackpad';
|
|
} else if (timeDelta > 400) {
|
|
// This is likely a new scroll action.
|
|
type = null;
|
|
lastValue = value;
|
|
|
|
// Start a timeout in case this was a singular event, and delay it by up
|
|
// to 40ms.
|
|
timeout = window.setTimeout(function setTimeout() {
|
|
var _type = 'wheel';
|
|
this._zoom(-this.state.mouseWheelLastValue, this.state.mouseWheelPos);
|
|
this.setState({mouseWheelType: _type});
|
|
}.bind(this), 40);
|
|
} else if (!this._type) {
|
|
// This is a repeating event, but we don't know the type of event just
|
|
// yet.
|
|
// If the delta per time is small, we assume it's a fast trackpad;
|
|
// otherwise we switch into wheel mode.
|
|
type = Math.abs(timeDelta * value) < 200 ? 'trackpad' : 'wheel';
|
|
|
|
// Make sure our delayed event isn't fired again, because we accumulate
|
|
// the previous event (which was less than 40ms ago) into this event.
|
|
if (timeout) {
|
|
window.clearTimeout(timeout);
|
|
timeout = null;
|
|
value += lastValue;
|
|
}
|
|
}
|
|
|
|
// Slow down zoom if shift key is held for more precise zooming
|
|
if (event.shiftKey && value) {
|
|
value = value / 4;
|
|
}
|
|
|
|
// Only fire the callback if we actually know what type of scrolling device
|
|
// the user uses.
|
|
if (type) {
|
|
this._zoom(-value, pos);
|
|
}
|
|
|
|
this.setState({
|
|
mouseWheelTime: time,
|
|
mouseWheelPos: pos,
|
|
mouseWheelType: type,
|
|
mouseWheelTimeout: timeout,
|
|
mouseWheelLastValue: lastValue
|
|
});
|
|
},
|
|
/* eslint-enable complexity, max-statements */
|
|
|
|
_zoom: function _zoom(delta, pos) {
|
|
|
|
// Scale by sigmoid of scroll wheel delta.
|
|
var scale = 2 / (1 + Math.exp(-Math.abs(delta / 100)));
|
|
if (delta < 0 && scale !== 0) {
|
|
scale = 1 / scale;
|
|
}
|
|
this.props.onZoom({pos: pos, scale: scale});
|
|
window.clearTimeout(this._zoomEndTimeout);
|
|
this._zoomEndTimeout = window.setTimeout(function _setTimeout() {
|
|
this.props.onZoomEnd();
|
|
}.bind(this), 200);
|
|
},
|
|
|
|
render: function render() {
|
|
return r.div({
|
|
ref: 'container',
|
|
onMouseMove: this._onMouseMove,
|
|
onMouseDown: this._onMouseDown,
|
|
onWheel: this._onWheel,
|
|
style: {
|
|
width: this.props.width,
|
|
height: this.props.height,
|
|
position: 'relative'
|
|
}
|
|
}, this.props.children);
|
|
}
|
|
});
|
|
|
|
module.exports = MapInteractions;
|