Merge pull request #13491 from ahocevar/map-loadstart-loadend
Add loadstart and loadend map events
This commit is contained in:
@@ -3,11 +3,13 @@ layout: example.html
|
|||||||
title: Image Load Events
|
title: Image Load Events
|
||||||
shortdesc: Example using image load events.
|
shortdesc: Example using image load events.
|
||||||
docs: >
|
docs: >
|
||||||
<p>Image sources fire events related to image loading. You can
|
Image sources fire events related to image loading. You can
|
||||||
listen for <code>imageloadstart</code>, <code>imageloadend</code>,
|
listen for <code>imageloadstart</code>, <code>imageloadend</code>,
|
||||||
and <code>imageloaderror</code> type events to monitor image loading
|
and <code>imageloaderror</code> type events to monitor image loading
|
||||||
progress. This example registers listeners for these events and
|
progress. This example registers listeners for these events and
|
||||||
renders an image loading progress bar at the bottom of the map.</p>
|
renders an image loading progress bar at the bottom of the map. The
|
||||||
|
progress bar is shown and hidden according to the map's <code>loadstart</code>
|
||||||
|
and <code>loadend</code> events.
|
||||||
tags: "image, events, loading"
|
tags: "image, events, loading"
|
||||||
---
|
---
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ function Progress(el) {
|
|||||||
* Increment the count of loading tiles.
|
* Increment the count of loading tiles.
|
||||||
*/
|
*/
|
||||||
Progress.prototype.addLoading = function () {
|
Progress.prototype.addLoading = function () {
|
||||||
if (this.loading === 0) {
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
++this.loading;
|
++this.loading;
|
||||||
this.update();
|
this.update();
|
||||||
};
|
};
|
||||||
@@ -29,11 +26,8 @@ Progress.prototype.addLoading = function () {
|
|||||||
* Increment the count of loaded tiles.
|
* Increment the count of loaded tiles.
|
||||||
*/
|
*/
|
||||||
Progress.prototype.addLoaded = function () {
|
Progress.prototype.addLoaded = function () {
|
||||||
const this_ = this;
|
++this.loaded;
|
||||||
setTimeout(function () {
|
this.update();
|
||||||
++this_.loaded;
|
|
||||||
this_.update();
|
|
||||||
}, 100);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,14 +36,6 @@ Progress.prototype.addLoaded = function () {
|
|||||||
Progress.prototype.update = function () {
|
Progress.prototype.update = function () {
|
||||||
const width = ((this.loaded / this.loading) * 100).toFixed(1) + '%';
|
const width = ((this.loaded / this.loading) * 100).toFixed(1) + '%';
|
||||||
this.el.style.width = width;
|
this.el.style.width = width;
|
||||||
if (this.loading === this.loaded) {
|
|
||||||
this.loading = 0;
|
|
||||||
this.loaded = 0;
|
|
||||||
const this_ = this;
|
|
||||||
setTimeout(function () {
|
|
||||||
this_.hide();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,10 +49,11 @@ Progress.prototype.show = function () {
|
|||||||
* Hide the progress bar.
|
* Hide the progress bar.
|
||||||
*/
|
*/
|
||||||
Progress.prototype.hide = function () {
|
Progress.prototype.hide = function () {
|
||||||
if (this.loading === this.loaded) {
|
const style = this.el.style;
|
||||||
this.el.style.visibility = 'hidden';
|
setTimeout(function () {
|
||||||
this.el.style.width = 0;
|
style.visibility = 'hidden';
|
||||||
}
|
style.width = 0;
|
||||||
|
}, 250);
|
||||||
};
|
};
|
||||||
|
|
||||||
const progress = new Progress(document.getElementById('progress'));
|
const progress = new Progress(document.getElementById('progress'));
|
||||||
@@ -80,11 +67,7 @@ const source = new ImageWMS({
|
|||||||
source.on('imageloadstart', function () {
|
source.on('imageloadstart', function () {
|
||||||
progress.addLoading();
|
progress.addLoading();
|
||||||
});
|
});
|
||||||
|
source.on(['imageloadend', 'imageloaderror'], function () {
|
||||||
source.on('imageloadend', function () {
|
|
||||||
progress.addLoaded();
|
|
||||||
});
|
|
||||||
source.on('imageloaderror', function () {
|
|
||||||
progress.addLoaded();
|
progress.addLoaded();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,3 +79,10 @@ const map = new Map({
|
|||||||
zoom: 4,
|
zoom: 4,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
map.on('loadstart', function () {
|
||||||
|
progress.show();
|
||||||
|
});
|
||||||
|
map.on('loadend', function () {
|
||||||
|
progress.hide();
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
.map {
|
||||||
|
background: #85ccf9;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#map {
|
||||||
|
height: 400px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinner {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner:after {
|
||||||
|
content: "";
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: -20px;
|
||||||
|
margin-left: -20px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 5px solid rgba(180, 180, 180, 0.6);
|
||||||
|
border-top-color: rgba(0, 0, 0, 0.6);
|
||||||
|
animation: spinner 0.6s linear infinite;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: Loading Spinner
|
||||||
|
shortdesc: Example using load events to show a loading spinner.
|
||||||
|
docs: >
|
||||||
|
You can listen for the map's <code>loadstart</code> and <code>loadend</code> events
|
||||||
|
to show a loading spinner on top of the map.
|
||||||
|
tags: "events, loading, spinner"
|
||||||
|
cloak:
|
||||||
|
- key: pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiY2t0cGdwMHVnMGdlbzMxbDhwazBic2xrNSJ9.WbcTL9uj8JPAsnT9mgb7oQ
|
||||||
|
value: Your Mapbox access token from https://mapbox.com/ here
|
||||||
|
---
|
||||||
|
<div id="map" class="map"></div>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import TileLayer from '../src/ol/layer/Tile.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import XYZ from '../src/ol/source/XYZ.js';
|
||||||
|
|
||||||
|
const key = 'get_your_own_D6rA4zTHduk6KOKTXzGB';
|
||||||
|
const attributions =
|
||||||
|
'<a href="https://www.maptiler.com/copyright/" target="_blank">© MapTiler</a> ' +
|
||||||
|
'<a href="https://www.openstreetmap.org/copyright" target="_blank">© OpenStreetMap contributors</a>';
|
||||||
|
|
||||||
|
const source = new XYZ({
|
||||||
|
attributions: attributions,
|
||||||
|
url: 'https://api.maptiler.com/maps/streets/{z}/{x}/{y}.png?key=' + key,
|
||||||
|
tileSize: 512,
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
layers: [new TileLayer({source: source})],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 2,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map.on('loadstart', function () {
|
||||||
|
map.getTargetElement().classList.add('spinner');
|
||||||
|
});
|
||||||
|
map.on('loadend', function () {
|
||||||
|
map.getTargetElement().classList.remove('spinner');
|
||||||
|
});
|
||||||
@@ -7,7 +7,9 @@ docs: >
|
|||||||
listen for <code>tileloadstart</code>, <code>tileloadend</code>,
|
listen for <code>tileloadstart</code>, <code>tileloadend</code>,
|
||||||
and <code>tileloaderror</code> type events to monitor tile loading
|
and <code>tileloaderror</code> type events to monitor tile loading
|
||||||
progress. This example registers listeners for these events and
|
progress. This example registers listeners for these events and
|
||||||
renders a tile loading progress bar at the bottom of the map.</p>
|
renders a tile loading progress bar at the bottom of the map. The
|
||||||
|
progress bar is shown and hidden according to the map's <code>loadstart</code>
|
||||||
|
and <code>loadend</code> events.
|
||||||
tags: "tile, events, loading"
|
tags: "tile, events, loading"
|
||||||
cloak:
|
cloak:
|
||||||
- key: pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiY2t0cGdwMHVnMGdlbzMxbDhwazBic2xrNSJ9.WbcTL9uj8JPAsnT9mgb7oQ
|
- key: pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiY2t0cGdwMHVnMGdlbzMxbDhwazBic2xrNSJ9.WbcTL9uj8JPAsnT9mgb7oQ
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ function Progress(el) {
|
|||||||
* Increment the count of loading tiles.
|
* Increment the count of loading tiles.
|
||||||
*/
|
*/
|
||||||
Progress.prototype.addLoading = function () {
|
Progress.prototype.addLoading = function () {
|
||||||
if (this.loading === 0) {
|
|
||||||
this.show();
|
|
||||||
}
|
|
||||||
++this.loading;
|
++this.loading;
|
||||||
this.update();
|
this.update();
|
||||||
};
|
};
|
||||||
@@ -29,11 +26,8 @@ Progress.prototype.addLoading = function () {
|
|||||||
* Increment the count of loaded tiles.
|
* Increment the count of loaded tiles.
|
||||||
*/
|
*/
|
||||||
Progress.prototype.addLoaded = function () {
|
Progress.prototype.addLoaded = function () {
|
||||||
const this_ = this;
|
++this.loaded;
|
||||||
setTimeout(function () {
|
this.update();
|
||||||
++this_.loaded;
|
|
||||||
this_.update();
|
|
||||||
}, 100);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,14 +36,6 @@ Progress.prototype.addLoaded = function () {
|
|||||||
Progress.prototype.update = function () {
|
Progress.prototype.update = function () {
|
||||||
const width = ((this.loaded / this.loading) * 100).toFixed(1) + '%';
|
const width = ((this.loaded / this.loading) * 100).toFixed(1) + '%';
|
||||||
this.el.style.width = width;
|
this.el.style.width = width;
|
||||||
if (this.loading === this.loaded) {
|
|
||||||
this.loading = 0;
|
|
||||||
this.loaded = 0;
|
|
||||||
const this_ = this;
|
|
||||||
setTimeout(function () {
|
|
||||||
this_.hide();
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,10 +49,11 @@ Progress.prototype.show = function () {
|
|||||||
* Hide the progress bar.
|
* Hide the progress bar.
|
||||||
*/
|
*/
|
||||||
Progress.prototype.hide = function () {
|
Progress.prototype.hide = function () {
|
||||||
if (this.loading === this.loaded) {
|
const style = this.el.style;
|
||||||
this.el.style.visibility = 'hidden';
|
setTimeout(function () {
|
||||||
this.el.style.width = 0;
|
style.visibility = 'hidden';
|
||||||
}
|
style.width = 0;
|
||||||
|
}, 250);
|
||||||
};
|
};
|
||||||
|
|
||||||
const progress = new Progress(document.getElementById('progress'));
|
const progress = new Progress(document.getElementById('progress'));
|
||||||
@@ -85,11 +72,7 @@ const source = new XYZ({
|
|||||||
source.on('tileloadstart', function () {
|
source.on('tileloadstart', function () {
|
||||||
progress.addLoading();
|
progress.addLoading();
|
||||||
});
|
});
|
||||||
|
source.on(['tileloadend', 'tileloaderror'], function () {
|
||||||
source.on('tileloadend', function () {
|
|
||||||
progress.addLoaded();
|
|
||||||
});
|
|
||||||
source.on('tileloaderror', function () {
|
|
||||||
progress.addLoaded();
|
progress.addLoaded();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -101,3 +84,10 @@ const map = new Map({
|
|||||||
zoom: 2,
|
zoom: 2,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
map.on('loadstart', function () {
|
||||||
|
progress.show();
|
||||||
|
});
|
||||||
|
map.on('loadend', function () {
|
||||||
|
progress.hide();
|
||||||
|
});
|
||||||
|
|||||||
+15
-1
@@ -26,8 +26,22 @@ export default {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
MOVEEND: 'moveend',
|
MOVEEND: 'moveend',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when loading of additional map data (tiles, images, features) starts.
|
||||||
|
* @event module:ol/render/Event~RenderEvent#loadstart
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
LOADSTART: 'loadstart',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when loading of additional map data has completed.
|
||||||
|
* @event module:ol/render/Event~RenderEvent#loadend
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
LOADEND: 'loadend',
|
||||||
};
|
};
|
||||||
|
|
||||||
/***
|
/***
|
||||||
* @typedef {'postrender'|'movestart'|'moveend'} Types
|
* @typedef {'postrender'|'movestart'|'moveend'|'loadstart'|'loadend'} Types
|
||||||
*/
|
*/
|
||||||
|
|||||||
+34
-15
@@ -211,10 +211,16 @@ class PluggableMap extends BaseObject {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean}
|
* @type {boolean|undefined}
|
||||||
*/
|
*/
|
||||||
this.renderComplete_;
|
this.renderComplete_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.loaded_ = true;
|
||||||
|
|
||||||
/** @private */
|
/** @private */
|
||||||
this.boundHandleBrowserEvent_ = this.handleBrowserEvent.bind(this);
|
this.boundHandleBrowserEvent_ = this.handleBrowserEvent.bind(this);
|
||||||
|
|
||||||
@@ -1187,17 +1193,26 @@ class PluggableMap extends BaseObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (frameState && this.renderer_ && !frameState.animate) {
|
||||||
frameState &&
|
if (this.renderComplete_ === true) {
|
||||||
this.renderer_ &&
|
if (this.hasListener(RenderEventType.RENDERCOMPLETE)) {
|
||||||
this.hasListener(RenderEventType.RENDERCOMPLETE) &&
|
this.renderer_.dispatchRenderEvent(
|
||||||
!frameState.animate &&
|
RenderEventType.RENDERCOMPLETE,
|
||||||
this.renderComplete_
|
frameState
|
||||||
) {
|
);
|
||||||
this.renderer_.dispatchRenderEvent(
|
}
|
||||||
RenderEventType.RENDERCOMPLETE,
|
if (this.loaded_ === false) {
|
||||||
frameState
|
this.loaded_ = true;
|
||||||
);
|
this.dispatchEvent(
|
||||||
|
new MapEvent(MapEventType.LOADEND, this, frameState)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (this.loaded_ === true) {
|
||||||
|
this.loaded_ = false;
|
||||||
|
this.dispatchEvent(
|
||||||
|
new MapEvent(MapEventType.LOADSTART, this, frameState)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const postRenderFunctions = this.postRenderFunctions_;
|
const postRenderFunctions = this.postRenderFunctions_;
|
||||||
@@ -1573,9 +1588,13 @@ class PluggableMap extends BaseObject {
|
|||||||
this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState));
|
this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState));
|
||||||
|
|
||||||
this.renderComplete_ =
|
this.renderComplete_ =
|
||||||
!this.tileQueue_.getTilesLoading() &&
|
this.hasListener(MapEventType.LOADSTART) ||
|
||||||
!this.tileQueue_.getCount() &&
|
this.hasListener(MapEventType.LOADEND) ||
|
||||||
!this.getLoadingOrNotReady();
|
this.hasListener(RenderEventType.RENDERCOMPLETE)
|
||||||
|
? !this.tileQueue_.getTilesLoading() &&
|
||||||
|
!this.tileQueue_.getCount() &&
|
||||||
|
!this.getLoadingOrNotReady()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
if (!this.postRenderTimeoutHandle_) {
|
if (!this.postRenderTimeoutHandle_) {
|
||||||
this.postRenderTimeoutHandle_ = setTimeout(() => {
|
this.postRenderTimeoutHandle_ = setTimeout(() => {
|
||||||
|
|||||||
@@ -505,6 +505,97 @@ describe('ol/Map', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('loadstart/loadend event sequence', function () {
|
||||||
|
let map;
|
||||||
|
beforeEach(function () {
|
||||||
|
const target = document.createElement('div');
|
||||||
|
target.style.width = '100px';
|
||||||
|
target.style.height = '100px';
|
||||||
|
document.body.appendChild(target);
|
||||||
|
map = new Map({
|
||||||
|
target: target,
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
opacity: 0.5,
|
||||||
|
source: new XYZ({
|
||||||
|
url: 'spec/ol/data/osm-{z}-{x}-{y}.png',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new ImageLayer({
|
||||||
|
source: new ImageStatic({
|
||||||
|
url: 'spec/ol/data/osm-0-0-0.png',
|
||||||
|
imageExtent: getProjection('EPSG:3857').getExtent(),
|
||||||
|
projection: 'EPSG:3857',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new VectorLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
url: 'spec/ol/data/point.json',
|
||||||
|
format: new GeoJSON(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new VectorLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
url: 'spec/ol/data/point.json',
|
||||||
|
format: new GeoJSON(),
|
||||||
|
strategy: tileStrategy(createXYZ()),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new VectorLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
features: [new Feature(new Point([0, 0]))],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new VectorLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
loader: function (extent, resolution, projection) {
|
||||||
|
this.addFeature(new Feature(new Point([0, 0])));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new WebGLPointsLayer({
|
||||||
|
source: new VectorSource({
|
||||||
|
features: [new Feature(new Point([0, 0]))],
|
||||||
|
}),
|
||||||
|
style: {
|
||||||
|
symbol: {
|
||||||
|
color: 'red',
|
||||||
|
symbolType: 'circle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
document.body.removeChild(map.getTargetElement());
|
||||||
|
map.setTarget(null);
|
||||||
|
map.dispose();
|
||||||
|
map.getLayers().forEach((layer) => layer.dispose());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('is a reliable start-end sequence', function (done) {
|
||||||
|
const layers = map.getLayers().getArray();
|
||||||
|
expect(layers[6].getRenderer().ready).to.be(false);
|
||||||
|
let loading = 0;
|
||||||
|
map.on('loadstart', () => {
|
||||||
|
map.getView().setZoom(0.1);
|
||||||
|
loading++;
|
||||||
|
});
|
||||||
|
map.on('loadend', () => {
|
||||||
|
expect(loading).to.be(1);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
map.setView(
|
||||||
|
new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('#getFeaturesAtPixel', function () {
|
describe('#getFeaturesAtPixel', function () {
|
||||||
let target, map, layer;
|
let target, map, layer;
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user