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
|
||||
shortdesc: Example using image load events.
|
||||
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>,
|
||||
and <code>imageloaderror</code> type events to monitor image loading
|
||||
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"
|
||||
---
|
||||
<div class="wrapper">
|
||||
|
||||
@@ -18,9 +18,6 @@ function Progress(el) {
|
||||
* Increment the count of loading tiles.
|
||||
*/
|
||||
Progress.prototype.addLoading = function () {
|
||||
if (this.loading === 0) {
|
||||
this.show();
|
||||
}
|
||||
++this.loading;
|
||||
this.update();
|
||||
};
|
||||
@@ -29,11 +26,8 @@ Progress.prototype.addLoading = function () {
|
||||
* Increment the count of loaded tiles.
|
||||
*/
|
||||
Progress.prototype.addLoaded = function () {
|
||||
const this_ = this;
|
||||
setTimeout(function () {
|
||||
++this_.loaded;
|
||||
this_.update();
|
||||
}, 100);
|
||||
++this.loaded;
|
||||
this.update();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -42,14 +36,6 @@ Progress.prototype.addLoaded = function () {
|
||||
Progress.prototype.update = function () {
|
||||
const width = ((this.loaded / this.loading) * 100).toFixed(1) + '%';
|
||||
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.
|
||||
*/
|
||||
Progress.prototype.hide = function () {
|
||||
if (this.loading === this.loaded) {
|
||||
this.el.style.visibility = 'hidden';
|
||||
this.el.style.width = 0;
|
||||
}
|
||||
const style = this.el.style;
|
||||
setTimeout(function () {
|
||||
style.visibility = 'hidden';
|
||||
style.width = 0;
|
||||
}, 250);
|
||||
};
|
||||
|
||||
const progress = new Progress(document.getElementById('progress'));
|
||||
@@ -80,11 +67,7 @@ const source = new ImageWMS({
|
||||
source.on('imageloadstart', function () {
|
||||
progress.addLoading();
|
||||
});
|
||||
|
||||
source.on('imageloadend', function () {
|
||||
progress.addLoaded();
|
||||
});
|
||||
source.on('imageloaderror', function () {
|
||||
source.on(['imageloadend', 'imageloaderror'], function () {
|
||||
progress.addLoaded();
|
||||
});
|
||||
|
||||
@@ -96,3 +79,10 @@ const map = new Map({
|
||||
zoom: 4,
|
||||
}),
|
||||
});
|
||||
|
||||
map.on('loadstart', function () {
|
||||
progress.show();
|
||||
});
|
||||
map.on('loadend', function () {
|
||||
progress.hide();
|
||||
});
|
||||
|
||||
30
examples/load-events.css
Normal file
30
examples/load-events.css
Normal file
@@ -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;
|
||||
}
|
||||
13
examples/load-events.html
Normal file
13
examples/load-events.html
Normal file
@@ -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>
|
||||
31
examples/load-events.js
Normal file
31
examples/load-events.js
Normal file
@@ -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>,
|
||||
and <code>tileloaderror</code> type events to monitor tile loading
|
||||
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"
|
||||
cloak:
|
||||
- key: pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiY2t0cGdwMHVnMGdlbzMxbDhwazBic2xrNSJ9.WbcTL9uj8JPAsnT9mgb7oQ
|
||||
|
||||
@@ -18,9 +18,6 @@ function Progress(el) {
|
||||
* Increment the count of loading tiles.
|
||||
*/
|
||||
Progress.prototype.addLoading = function () {
|
||||
if (this.loading === 0) {
|
||||
this.show();
|
||||
}
|
||||
++this.loading;
|
||||
this.update();
|
||||
};
|
||||
@@ -29,11 +26,8 @@ Progress.prototype.addLoading = function () {
|
||||
* Increment the count of loaded tiles.
|
||||
*/
|
||||
Progress.prototype.addLoaded = function () {
|
||||
const this_ = this;
|
||||
setTimeout(function () {
|
||||
++this_.loaded;
|
||||
this_.update();
|
||||
}, 100);
|
||||
++this.loaded;
|
||||
this.update();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -42,14 +36,6 @@ Progress.prototype.addLoaded = function () {
|
||||
Progress.prototype.update = function () {
|
||||
const width = ((this.loaded / this.loading) * 100).toFixed(1) + '%';
|
||||
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.
|
||||
*/
|
||||
Progress.prototype.hide = function () {
|
||||
if (this.loading === this.loaded) {
|
||||
this.el.style.visibility = 'hidden';
|
||||
this.el.style.width = 0;
|
||||
}
|
||||
const style = this.el.style;
|
||||
setTimeout(function () {
|
||||
style.visibility = 'hidden';
|
||||
style.width = 0;
|
||||
}, 250);
|
||||
};
|
||||
|
||||
const progress = new Progress(document.getElementById('progress'));
|
||||
@@ -85,11 +72,7 @@ const source = new XYZ({
|
||||
source.on('tileloadstart', function () {
|
||||
progress.addLoading();
|
||||
});
|
||||
|
||||
source.on('tileloadend', function () {
|
||||
progress.addLoaded();
|
||||
});
|
||||
source.on('tileloaderror', function () {
|
||||
source.on(['tileloadend', 'tileloaderror'], function () {
|
||||
progress.addLoaded();
|
||||
});
|
||||
|
||||
@@ -101,3 +84,10 @@ const map = new Map({
|
||||
zoom: 2,
|
||||
}),
|
||||
});
|
||||
|
||||
map.on('loadstart', function () {
|
||||
progress.show();
|
||||
});
|
||||
map.on('loadend', function () {
|
||||
progress.hide();
|
||||
});
|
||||
|
||||
@@ -26,8 +26,22 @@ export default {
|
||||
* @api
|
||||
*/
|
||||
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
|
||||
*/
|
||||
|
||||
@@ -211,10 +211,16 @@ class PluggableMap extends BaseObject {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {boolean}
|
||||
* @type {boolean|undefined}
|
||||
*/
|
||||
this.renderComplete_;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.loaded_ = true;
|
||||
|
||||
/** @private */
|
||||
this.boundHandleBrowserEvent_ = this.handleBrowserEvent.bind(this);
|
||||
|
||||
@@ -1187,17 +1193,26 @@ class PluggableMap extends BaseObject {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
frameState &&
|
||||
this.renderer_ &&
|
||||
this.hasListener(RenderEventType.RENDERCOMPLETE) &&
|
||||
!frameState.animate &&
|
||||
this.renderComplete_
|
||||
) {
|
||||
this.renderer_.dispatchRenderEvent(
|
||||
RenderEventType.RENDERCOMPLETE,
|
||||
frameState
|
||||
);
|
||||
if (frameState && this.renderer_ && !frameState.animate) {
|
||||
if (this.renderComplete_ === true) {
|
||||
if (this.hasListener(RenderEventType.RENDERCOMPLETE)) {
|
||||
this.renderer_.dispatchRenderEvent(
|
||||
RenderEventType.RENDERCOMPLETE,
|
||||
frameState
|
||||
);
|
||||
}
|
||||
if (this.loaded_ === false) {
|
||||
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_;
|
||||
@@ -1573,9 +1588,13 @@ class PluggableMap extends BaseObject {
|
||||
this.dispatchEvent(new MapEvent(MapEventType.POSTRENDER, this, frameState));
|
||||
|
||||
this.renderComplete_ =
|
||||
!this.tileQueue_.getTilesLoading() &&
|
||||
!this.tileQueue_.getCount() &&
|
||||
!this.getLoadingOrNotReady();
|
||||
this.hasListener(MapEventType.LOADSTART) ||
|
||||
this.hasListener(MapEventType.LOADEND) ||
|
||||
this.hasListener(RenderEventType.RENDERCOMPLETE)
|
||||
? !this.tileQueue_.getTilesLoading() &&
|
||||
!this.tileQueue_.getCount() &&
|
||||
!this.getLoadingOrNotReady()
|
||||
: undefined;
|
||||
|
||||
if (!this.postRenderTimeoutHandle_) {
|
||||
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 () {
|
||||
let target, map, layer;
|
||||
beforeEach(function () {
|
||||
|
||||
Reference in New Issue
Block a user