/* Copyright (c) 2020 Jean-Marc VIGLINO, released under the CeCILL-B license (French BSD license) (http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt). */ import ol_Overlay from 'ol/Overlay.js' import {unByKey as ol_Observable_unByKey} from 'ol/Observable.js' import ol_ext_element from '../util/element.js'; import ol_particule_Base from '../particule/Base.js'; /** An overlay to play animations on top of the map * The overlay define a set of particules animated on top of the map. * Particules are objects with coordinates. * They are dawn in a canvas using the draw particule method. * The update particule method updates the particule position according to the timelapse * * @constructor * @extends {ol_Overlay} * @param {*} options * @param {String} options.className class of the Overlay * @param {number} option.density particule density, default .5 * @param {number} option.speed particule speed, default 4 * @param {number} option.angle particule angle in radian, default PI/4 * @param {boolean} options.animate start animation, default true * @param {number} options.fps frame per second, default 25 */ var ol_Overlay_AnimatedCanvas = class olOverlayAnimatedCanvas extends ol_Overlay { constructor(options) { options = options || {}; var canvas = ol_ext_element.create('CANVAS', { className: ((options.className || '') + ' ol-animated-overlay').trim() }); super({ element: canvas, stopEvent: false }); this._canvas = canvas; this._ctx = this._canvas.getContext('2d'); this._listener = []; this._time = 0; this._particuleClass = options.particule || ol_particule_Base; if (options.createParticule) this._createParticule = options.createParticule; // 25fps this._fps = 1000 / (options.fps || 25); // Default particules properties var p = this._createParticule(); this._psize = p.get('size') || [50, 50]; this.set('density', options.density || .5); this.set('speed', options.speed || 4); this.set('angle', typeof (options.angle) === 'number' ? options.angle : Math.PI / 4); if (options.animate !== false) this.setAnimation(true); // Prevent animation when window on background document.addEventListener("visibilitychange", function () { this._pause = true; }.bind(this)); } /** Set the visibility * @param {boolean} b */ setVisible(b) { this.element.style.display = b ? 'block' : 'none'; if (b) this.setAnimation(this.get('animation')); } /** Get the visibility * @return {boolean} b */ getVisible() { return this.element.style.display != 'none'; } /** No update for this overlay */ updatePixelPosition() { } /** * Set the map instance the overlay is associated with * @param {ol.Map} map The map instance. */ setMap(map) { /* if (this.getMap()) { this.getMap().getViewport().querySelector('.ol-overlaycontainer').removeChild(this._canvas); } */ this._listener.forEach(function (l) { ol_Observable_unByKey(l); }); this._listener = []; super.setMap(map); if (map) { var size = map.getSize(); this._canvas.width = size[0]; this._canvas.height = size[1]; this.draw(); this._listener.push(map.on('change:size', function () { var size = map.getSize(); if (this._canvas.width !== size[0] || this._canvas.height !== size[1]) { this._canvas.width = size[0]; this._canvas.height = size[1]; this.draw(); } }.bind(this))); } this.setAnimation(!!map); } /** Create particules or return exiting ones */ getParticules() { var w = this._psize[0]; var h = this._psize[1]; var d = (this.get('density') * this._canvas.width * this._canvas.height / w / h) << 0; if (!this._particules) this._particules = []; if (d > this._particules.length) { for (var i = this._particules.length; i < d; i++) { this._particules.push(this._createParticule(this, this.randomCoord())); } } else { this._particules.length = d; } return this._particules; } /** Create a particule * @private */ _createParticule(overlay, coordinate) { return new this._particuleClass({ overlay: overlay, coordinate: coordinate }); } /** Get random coordinates on canvas */ randomCoord() { return [ Math.random() * (this._canvas.width + this._psize[0]) - this._psize[0] / 2, Math.random() * (this._canvas.height + this._psize[1]) - this._psize[1] / 2 ]; } /** Draw canvas overlay (draw each particules) * @param {number} dt timelapes since last call */ draw(dt) { var ctx = this._ctx; this.clear(); ctx.beginPath(); this.getParticules().forEach(function (p) { if (dt) { p.update(dt); this.testExit(p); } p.draw(this._ctx); }.bind(this)); } /** Test if particule exit the canvas and add it on other side * @param {*} p the point to test * @param {ol.size} size size of the overlap */ testExit(p) { var size = this._psize; if (p.coordinate[0] < -size[0]) { p.coordinate[0] = this._canvas.width + size[0]; p.coordinate[1] = Math.random() * (this._canvas.height + size[1]) - size[1] / 2; } else if (p.coordinate[0] > this._canvas.width + size[0]) { p.coordinate[0] = -size[0]; p.coordinate[1] = Math.random() * (this._canvas.height + size[1]) - size[1] / 2; } else if (p.coordinate[1] < -size[1]) { p.coordinate[0] = Math.random() * (this._canvas.width + size[0]) - size[0] / 2; p.coordinate[1] = this._canvas.height + size[1]; } else if (p.coordinate[1] > this._canvas.height + size[1]) { p.coordinate[0] = Math.random() * (this._canvas.width + size[0]) - size[0] / 2; p.coordinate[1] = -size[1]; } } /** Clear canvas */ clear() { this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); } /** Get overlay canvas * @return {CanvasElement} */ getCanvas() { return this._canvas; } /** Set canvas animation * @param {boolean} anim, default true * @api */ setAnimation(anim) { anim = (anim !== false); this.set('animation', anim); if (anim) { this._pause = true; requestAnimationFrame(this._animate.bind(this)); } else { this.dispatchEvent({ type: 'animation:stop', time: this._time }); } } /** * @private */ _animate(time) { if (this.getVisible() && this.get('animation')) { if (this._pause) { // reset time requestAnimationFrame(function (time) { this._time = time; requestAnimationFrame(this._animate.bind(this)); }.bind(this)); } else { // Test fps if (time - this._time > this._fps) { this.draw(time - this._time); this._time = time; } requestAnimationFrame(this._animate.bind(this)); } } this._pause = false; } } export default ol_Overlay_AnimatedCanvas