Reset Bounds when surrounding div resizes (#219)

* added listeners for element/map resize event

* changed names accordingly

* added eslint-disabled to pass through travis checks

* changes for travis checks

* added eslint disables

* disabling eslint for lNo 906

* removed trailing spaces

* removed unnecessary id and refs

* removed eventListners on unMount
This commit is contained in:
Rupesh Singh 2016-08-22 20:01:02 +05:30 committed by Ivan Starkov
parent 7542b69310
commit e196e9f1e5
6 changed files with 307 additions and 9 deletions

110
develop/GMapResizable.js Normal file
View File

@ -0,0 +1,110 @@
/* eslint-disable */
import React, { PropTypes } from 'react';
import compose from 'recompose/compose';
import defaultProps from 'recompose/defaultProps';
import withStateSelector from './utils/withStateSelector';
import withHandlers from 'recompose/withHandlers';
import withState from 'recompose/withState';
import withContext from 'recompose/withContext';
import withProps from 'recompose/withProps';
import withPropsOnChange from 'recompose/withPropsOnChange';
import ptInBounds from './utils/ptInBounds';
import GoogleMapReact from '../src';
import SimpleMarker from './markers/SimpleMarker';
import { createSelector } from 'reselect';
import { susolvkaCoords, generateMarkers } from './data/fakeData';
export const gMapResizable = ({
style, hoverDistance, options,
mapParams: { center, zoom },
onChange, onChildMouseEnter, onChildMouseLeave,
markers, draggable, // hoveredMarkerId,
}) => (
<GoogleMapReact
draggable={draggable}
style={style}
options={options}
hoverDistance={hoverDistance}
center={center}
zoom={zoom}
onChange={onChange}
onChildMouseEnter={onChildMouseEnter}
onChildMouseLeave={onChildMouseLeave}
resetBoundsOnResize={true}
apiKey={"AIzaSyC-BebC7ChnHPzxQm7DAHYFMCqR5H3Jlps"}
>
{
markers
}
</GoogleMapReact>
);
export const gMapHOC = compose(
defaultProps({
clusterRadius: 60,
hoverDistance: 30,
options: {
minZoom: 3,
maxZoom: 15,
},
style: {
position: 'relative',
margin: 10,
padding: 10,
flex: 1,
},
}),
withContext(
{ hello: PropTypes.string },
() => ({ hello: 'world' })
),
// withState so you could change markers if you want
withStateSelector(
'markers',
'setMarkers',
() => createSelector(
({ route: { markersCount = 20 } }) => markersCount,
(markersCount) => generateMarkers(markersCount)
)
),
withState('hoveredMarkerId', 'setHoveredMarkerId', -1),
withState('mapParams', 'setMapParams', { center: susolvkaCoords, zoom: 6 }),
// describe events
withHandlers({
onChange: ({ setMapParams }) => ({ center, zoom, bounds }) => {
setMapParams({ center, zoom, bounds });
},
onChildMouseEnter: ({ setHoveredMarkerId }) => (hoverKey, { id }) => {
setHoveredMarkerId(id);
},
onChildMouseLeave: ({ setHoveredMarkerId }) => () => {
setHoveredMarkerId(-1);
},
}),
withPropsOnChange(
['markers', 'mapParams'],
({ markers, mapParams: { bounds } }) => ({
markers: bounds
? markers.filter(m => ptInBounds(bounds, m))
: [],
})
),
withProps(({ hoveredMarkerId }) => ({
draggable: hoveredMarkerId === -1,
})),
withPropsOnChange(
['markers'],
({ markers }) => ({
markers: markers
.map(({ ...markerProps, id }) => (
<SimpleMarker
key={id}
id={id}
{...markerProps}
/>
)),
})
)
);
export default gMapHOC(gMapResizable);

View File

@ -14,6 +14,7 @@ export class Layout extends Component { // eslint-disable-line
<Link to="/">Multi Markers</Link>
<Link to="/hoverunoptim">Hover unoptim</Link>
<Link to="/hoveroptim">Hover optim</Link>
<Link to="/resizable">Resizable Map</Link>
</div>
<div>
<a href="https://github.com/istarkov/google-map-clustering-example">

View File

@ -1,9 +1,9 @@
.layout
display: flex
min-height: 100vh
min-height: 90vh
flex-direction: column
margin: 0 1px 0 1px
width: 100vh
.header
height: 2em
background-color: #004336
@ -29,6 +29,8 @@
.main
flex: 1
resize: both
overflow: auto
display: flex
.footer
@ -39,4 +41,4 @@
align-items: center
justify-content: center
a
color: #fff
color: #fff

View File

@ -5,6 +5,7 @@ import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import Layout from './Layout';
import GMap from './GMap';
import GMapOptim from './GMapOptim';
import GMapResizable from './GMapResizable';
import 'normalize.css/normalize.css';
import './Main.sass';
@ -16,6 +17,7 @@ render(
<Route path="/" component={Layout}>
<Route markersCount={50} path="hoverunoptim" component={GMap} />
<Route markersCount={50} path="hoveroptim" component={GMapOptim} />
<Route markersCount={20} path="resizable" component={GMapResizable} />
<IndexRoute markersCount={20} component={GMap} />
</Route>
</Router>

View File

@ -22,6 +22,7 @@ import log2 from './utils/math/log2';
import isNumber from './utils/isNumber';
import omit from './utils/omit';
import detectElementResize from './utils/detectElementResize';
const kEPS = 0.00001;
const K_GOOGLE_TILE_SIZE = 256;
@ -89,6 +90,7 @@ export default class GoogleMap extends Component {
yesIWantToUseGoogleMapApiInternals: PropTypes.bool,
draggable: PropTypes.bool,
style: PropTypes.any,
resetBoundsOnResize: PropTypes.bool,
};
static defaultProps = {
@ -184,7 +186,7 @@ export default class GoogleMap extends Component {
this.mounted_ = true;
window.addEventListener('resize', this._onWindowResize);
window.addEventListener('keydown', this._onKeyDownCapture, true);
const mapDom = ReactDOM.findDOMNode(this.refs.google_map_dom);
// gmap can't prevent map drag if mousedown event already occured
// the only workaround I find is prevent mousedown native browser event
ReactDOM.findDOMNode(this.refs.google_map_dom)
@ -205,6 +207,10 @@ export default class GoogleMap extends Component {
this._initMap();
}
}, 0, this);
if (this.props.resetBoundsOnResize) {
const that = this;
detectElementResize.addResizeListener(mapDom, that._mapDomResizeCallback);
}
}
@ -301,12 +307,13 @@ export default class GoogleMap extends Component {
componentWillUnmount() {
this.mounted_ = false;
const that = this;
const mapDom = ReactDOM.findDOMNode(this.refs.google_map_dom);
window.removeEventListener('resize', this._onWindowResize);
window.removeEventListener('keydown', this._onKeyDownCapture);
ReactDOM.findDOMNode(this.refs.google_map_dom)
.removeEventListener('mousedown', this._onMapMouseDownNative, true);
mapDom.removeEventListener('mousedown', this._onMapMouseDownNative, true);
window.removeEventListener('mouseup', this._onChildMouseUp, false);
detectElementResize.addResizeListener(mapDom, that._mapDomResizeCallback);
if (this.overlay_) {
// this triggers overlay_.onRemove(), which will unmount the <GoogleMapMarkers/>
@ -326,7 +333,6 @@ export default class GoogleMap extends Component {
delete this.map_;
delete this.markersDispatcher_;
}
// calc minZoom if map size available
// it's better to not set minZoom less than this calculation gives
// otherwise there is no homeomorphism between screen coordinates and map
@ -364,6 +370,13 @@ export default class GoogleMap extends Component {
return minZoom;
}
_mapDomResizeCallback = () => {
this.resetSizeOnIdle_ = true;
if (this.maps_) {
this.maps_.event.trigger(this.map_, 'resize');
}
}
_initMap = () => {
// only initialize the map once
if (this.initialized_) {
@ -886,7 +899,9 @@ export default class GoogleMap extends Component {
[centerLatLng.lat, centerLatLng.lng], [gmC.lat(), gmC.lng()]);
}
if (!isArraysEqualEps(bounds, [ne.lat(), sw.lng(), sw.lat(), ne.lng()], kEPS)) {
if (!isArraysEqualEps(bounds, // eslint-disable-line
[ne.lat(), sw.lng(), sw.lat(), ne.lng()], kEPS // eslint-disable-line no-console
) && !this.props.resetBoundsOnResize) { // eslint-disable-line no-console
// this is normal if this message occured on resize
console.info('GoogleMap bounds not eq:', '\n', // eslint-disable-line no-console
bounds, '\n', [ne.lat(), sw.lng(), sw.lat(), ne.lng()]);

View File

@ -0,0 +1,168 @@
/* eslint-disable */
/**
* Detect Element Resize.
* Forked in order to guard against unsafe 'window' and 'document' references.
*
* https://github.com/sdecima/javascript-detect-element-resize
* Sebastian Decima
*
* version: 0.5.3
**/
// Check `document` and `window` in case of server-side rendering
var _window
if (typeof window !== 'undefined') {
_window = window
} else if (typeof self !== 'undefined') {
_window = self
} else {
_window = this
}
var attachEvent = typeof document !== 'undefined' && document.attachEvent;
var stylesCreated = false;
if (!attachEvent) {
var requestFrame = (function(){
var raf = _window.requestAnimationFrame || _window.mozRequestAnimationFrame || _window.webkitRequestAnimationFrame ||
function(fn){ return _window.setTimeout(fn, 20); };
return function(fn){ return raf(fn); };
})();
var cancelFrame = (function(){
var cancel = _window.cancelAnimationFrame || _window.mozCancelAnimationFrame || _window.webkitCancelAnimationFrame ||
_window.clearTimeout;
return function(id){ return cancel(id); };
})();
var resetTriggers = function(element){
var triggers = element.__resizeTriggers__,
expand = triggers.firstElementChild,
contract = triggers.lastElementChild,
expandChild = expand.firstElementChild;
contract.scrollLeft = contract.scrollWidth;
contract.scrollTop = contract.scrollHeight;
expandChild.style.width = expand.offsetWidth + 1 + 'px';
expandChild.style.height = expand.offsetHeight + 1 + 'px';
expand.scrollLeft = expand.scrollWidth;
expand.scrollTop = expand.scrollHeight;
};
var checkTriggers = function(element){
return element.offsetWidth != element.__resizeLast__.width ||
element.offsetHeight != element.__resizeLast__.height;
}
var scrollListener = function(e){
var element = this;
resetTriggers(this);
if (this.__resizeRAF__) cancelFrame(this.__resizeRAF__);
this.__resizeRAF__ = requestFrame(function(){
if (checkTriggers(element)) {
element.__resizeLast__.width = element.offsetWidth;
element.__resizeLast__.height = element.offsetHeight;
element.__resizeListeners__.forEach(function(fn){
fn.call(element, e);
});
}
});
};
/* Detect CSS Animations support to detect element display/re-attach */
var animation = false,
animationstring = 'animation',
keyframeprefix = '',
animationstartevent = 'animationstart',
domPrefixes = 'Webkit Moz O ms'.split(' '),
startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(' '),
pfx = '';
{
var elm = document.createElement('fakeelement');
if( elm.style.animationName !== undefined ) { animation = true; }
if( animation === false ) {
for( var i = 0; i < domPrefixes.length; i++ ) {
if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) {
pfx = domPrefixes[ i ];
animationstring = pfx + 'Animation';
keyframeprefix = '-' + pfx.toLowerCase() + '-';
animationstartevent = startEvents[ i ];
animation = true;
break;
}
}
}
}
var animationName = 'resizeanim';
var animationKeyframes = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { opacity: 0; } to { opacity: 0; } } ';
var animationStyle = keyframeprefix + 'animation: 1ms ' + animationName + '; ';
}
var createStyles = function() {
if (!stylesCreated) {
//opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360
var css = (animationKeyframes ? animationKeyframes : '') +
'.resize-triggers { ' + (animationStyle ? animationStyle : '') + 'visibility: hidden; opacity: 0; } ' +
'.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: \" \"; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }',
head = document.head || document.getElementsByTagName('head')[0],
style = document.createElement('style');
style.type = 'text/css';
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
stylesCreated = true;
}
}
var addResizeListener = function(element, fn){
if (element.parentNode === undefined) {
var tempParentDiv = document.createElement('div');
element.parentNode = tempParentDiv;
}
element = element.parentNode;
if (attachEvent) element.attachEvent('onresize', fn);
else {
if (!element.__resizeTriggers__) {
if (getComputedStyle(element).position == 'static') element.style.position = 'relative';
createStyles();
element.__resizeLast__ = {};
element.__resizeListeners__ = [];
(element.__resizeTriggers__ = document.createElement('div')).className = 'resize-triggers';
element.__resizeTriggers__.innerHTML = '<div class="expand-trigger"><div></div></div>' +
'<div class="contract-trigger"></div>';
element.appendChild(element.__resizeTriggers__);
resetTriggers(element);
element.addEventListener('scroll', scrollListener, true);
/* Listen for a css animation to detect element display/re-attach */
animationstartevent && element.__resizeTriggers__.addEventListener(animationstartevent, function(e) {
if(e.animationName == animationName)
resetTriggers(element);
});
}
element.__resizeListeners__.push(fn);
}
};
var removeResizeListener = function(element, fn){
element = element.parentNode;
if (attachEvent) element.detachEvent('onresize', fn);
else {
element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1);
if (!element.__resizeListeners__.length) {
element.removeEventListener('scroll', scrollListener);
element.__resizeTriggers__ = !element.removeChild(element.__resizeTriggers__);
}
}
}
module.exports = {
addResizeListener : addResizeListener,
removeResizeListener : removeResizeListener
};