diff --git a/src/ol/map.js b/src/ol/map.js index c20c7e1f87..5a319c4965 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -210,7 +210,7 @@ ol.Map = function(options) { * @private * @type {ol.Extent} */ - this.previousExtent_ = ol.extent.createEmpty(); + this.previousExtent_ = null; /** * @private @@ -1200,6 +1200,7 @@ ol.Map.prototype.renderFrame_ = function(time) { var size = this.getSize(); var view = this.getView(); var extent = ol.extent.createEmpty(); + var previousFrameState = this.frameState_; /** @type {?olx.FrameState} */ var frameState = null; if (size !== undefined && ol.size.hasArea(size) && view && view.isDef()) { @@ -1249,7 +1250,19 @@ ol.Map.prototype.renderFrame_ = function(time) { Array.prototype.push.apply( this.postRenderFunctions_, frameState.postRenderFunctions); - var idle = !frameState.viewHints[ol.ViewHint.ANIMATING] && + if (previousFrameState) { + var moveStart = !this.previousExtent_ || + (!ol.extent.isEmpty(this.previousExtent_) && + !ol.extent.equals(frameState.extent, this.previousExtent_)); + if (moveStart) { + this.dispatchEvent( + new ol.MapEvent(ol.MapEventType.MOVESTART, this, previousFrameState)); + this.previousExtent_ = ol.extent.createOrUpdateEmpty(this.previousExtent_); + } + } + + var idle = this.previousExtent_ && + !frameState.viewHints[ol.ViewHint.ANIMATING] && !frameState.viewHints[ol.ViewHint.INTERACTING] && !ol.extent.equals(frameState.extent, this.previousExtent_); diff --git a/src/ol/mapeventtype.js b/src/ol/mapeventtype.js index db93742ebd..43d707cb56 100644 --- a/src/ol/mapeventtype.js +++ b/src/ol/mapeventtype.js @@ -12,6 +12,13 @@ ol.MapEventType = { */ POSTRENDER: 'postrender', + /** + * Triggered when the map starts moving. + * @event ol.MapEvent#movestart + * @api + */ + MOVESTART: 'movestart', + /** * Triggered after the map is moved. * @event ol.MapEvent#moveend diff --git a/test/spec/ol/map.test.js b/test/spec/ol/map.test.js index 88d47344bf..28d615389e 100644 --- a/test/spec/ol/map.test.js +++ b/test/spec/ol/map.test.js @@ -99,7 +99,7 @@ describe('ol.Map', function() { }); }); - describe('moveend event', function() { + describe('movestart/moveend event', function() { var target, view, map; @@ -135,13 +135,18 @@ describe('ol.Map', function() { document.body.removeChild(target); }); - it('is fired only once after view changes', function(done) { + it('are fired only once after view changes', function(done) { var center = [10, 20]; var zoom = 3; - var calls = 0; + var startCalls = 0; + var endCalls = 0; + map.on('movestart', function() { + ++startCalls; + expect(startCalls).to.be(1); + }); map.on('moveend', function() { - ++calls; - expect(calls).to.be(1); + ++endCalls; + expect(endCalls).to.be(1); expect(view.getCenter()).to.eql(center); expect(view.getZoom()).to.be(zoom); window.setTimeout(done, 1000); @@ -151,6 +156,31 @@ describe('ol.Map', function() { view.setZoom(zoom); }); + it('are fired in sequence', function(done) { + view.setCenter([0, 0]); + view.setResolution(0.703125); + map.renderSync(); + var center = [10, 20]; + var zoom = 3; + var calls = []; + map.on('movestart', function(e) { + calls.push('start'); + expect(calls).to.eql(['start']); + expect(e.frameState.viewState.center).to.eql([0, 0]); + expect(e.frameState.viewState.resolution).to.be(0.703125); + }); + map.on('moveend', function() { + calls.push('end'); + expect(calls).to.eql(['start', 'end']); + expect(view.getCenter()).to.eql(center); + expect(view.getZoom()).to.be(zoom); + done(); + }); + + view.setCenter(center); + view.setZoom(zoom); + }); + }); describe('#forEachLayerAtPixel()', function() {