Create marker labels lazily when showLabels is false

This commit is contained in:
James Lyne 2021-07-29 17:46:11 +01:00
parent 02c705de36
commit a7fa366635
4 changed files with 103 additions and 45 deletions

View File

@ -47,8 +47,14 @@ export class GenericIcon extends DivIcon {
// @ts-ignore // @ts-ignore
options: GenericIconOptions; options: GenericIconOptions;
_image?: HTMLImageElement;
_label?: HTMLSpanElement; private _image?: HTMLImageElement;
private _label?: HTMLSpanElement;
private _container?: HTMLDivElement;
private _labelCreated: boolean = false;
private _onHover: EventListener = () => {
this.createLabel();
};
constructor(options: GenericIconOptions) { constructor(options: GenericIconOptions) {
super(Object.assign(GenericIcon.defaultOptions, options)); super(Object.assign(GenericIcon.defaultOptions, options));
@ -64,14 +70,41 @@ export class GenericIcon extends DivIcon {
size = point(this.options.iconSize as PointExpression); size = point(this.options.iconSize as PointExpression);
this._image = markerIcon.cloneNode(false) as HTMLImageElement; this._image = markerIcon.cloneNode(false) as HTMLImageElement;
this._label = markerLabel.cloneNode(false) as HTMLSpanElement;
const sizeClass = [size.x, size.y].join('x');
this._image.width = size.x; this._image.width = size.x;
this._image.height = size.y; this._image.height = size.y;
this._image.src = url; this._image.src = url;
// @ts-ignore
Icon.prototype._setIconStyles.call(this, div, 'icon');
div.appendChild(this._image);
div.classList.add('marker');
if(this.options.className) {
div.classList.add(this.options.className);
}
//Create label lazily on hover
this._image.addEventListener('mouseover', this._onHover);
this._container = div;
return div;
}
createLabel() {
if(!this._container || this._labelCreated) {
return;
}
this._image?.removeEventListener('mouseover', this._onHover);
const size = point(this.options.iconSize as PointExpression),
sizeClass = [size.x, size.y].join('x');
this._label = markerLabel.cloneNode(false) as HTMLSpanElement;
this._label.classList.add(/*'markerName_' + set.id,*/ `marker__label--${sizeClass}`); this._label.classList.add(/*'markerName_' + set.id,*/ `marker__label--${sizeClass}`);
if (this.options.isHtml) { if (this.options.isHtml) {
@ -80,18 +113,8 @@ export class GenericIcon extends DivIcon {
this._label.textContent = this.options.label; this._label.textContent = this.options.label;
} }
// @ts-ignore this._container!.appendChild(this._label);
Icon.prototype._setIconStyles.call(this, div, 'icon'); this._labelCreated = true;
div.appendChild(this._image);
div.appendChild(this._label);
div.classList.add('marker');
if(this.options.className) {
div.classList.add(this.options.className);
}
return div;
} }
update(options: GenericIconOptions) { update(options: GenericIconOptions) {

View File

@ -15,6 +15,7 @@
*/ */
import {Layer, Map as LeafletMap, LayerGroup, LayerOptions, Util, Marker, Path} from "leaflet"; import {Layer, Map as LeafletMap, LayerGroup, LayerOptions, Util, Marker, Path} from "leaflet";
import {GenericMarker} from "@/leaflet/marker/GenericMarker";
export interface LiveAtlasLayerGroupOptions extends LayerOptions { export interface LiveAtlasLayerGroupOptions extends LayerOptions {
id: string; //Added to the name of layer group panes id: string; //Added to the name of layer group panes
@ -29,11 +30,11 @@ export interface LiveAtlasLayerGroupOptions extends LayerOptions {
export default class LiveAtlasLayerGroup extends LayerGroup { export default class LiveAtlasLayerGroup extends LayerGroup {
// @ts-ignore // @ts-ignore
options: LiveAtlasLayerGroupOptions; options: LiveAtlasLayerGroupOptions;
_zoomLimitedLayers: Set<Layer>; //Layers which are zoom limited and should be checked on zoom private _zoomLimitedLayers: Set<Layer>; //Layers which are zoom limited and should be checked on zoom
_layers: any; _layers: any;
_markerPane?: HTMLElement; _markerPane?: HTMLElement;
_zoomEndCallback = () => this._updateLayerVisibility(); private _zoomEndCallback = () => this._updateLayerVisibility();
constructor(options: LiveAtlasLayerGroupOptions) { constructor(options: LiveAtlasLayerGroupOptions) {
super([], options); super([], options);
@ -80,7 +81,7 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
layer.options.pane = `vectors`; layer.options.pane = `vectors`;
} }
const zoomLimited = this._isLayerZoomLimited(layer); const zoomLimited = LiveAtlasLayerGroup._isLayerZoomLimited(layer);
if (zoomLimited) { if (zoomLimited) {
this._zoomLimitedLayers.add(layer); this._zoomLimitedLayers.add(layer);
@ -89,11 +90,11 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
if (this._map) { if (this._map) {
//If layer is zoom limited, only add to map if it should be visible //If layer is zoom limited, only add to map if it should be visible
if (zoomLimited) { if (zoomLimited) {
if (this._isLayerVisible(layer, this._map.getZoom())) { if (LiveAtlasLayerGroup._isLayerVisible(layer, this._map.getZoom())) {
this._map.addLayer(layer); this._addToMap(layer);
} }
} else { } else {
this._map.addLayer(layer); this._addToMap(layer);
} }
} }
@ -106,7 +107,19 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
} }
update(options: LiveAtlasLayerGroupOptions) { update(options: LiveAtlasLayerGroupOptions) {
this.options.showLabels = options.showLabels; if(this.options.showLabels !== options.showLabels) {
//Create labels if they are now always visible
//TODO: This will be slow when many markers exist. Is it worth doing?
if(options.showLabels) {
this.eachLayer((layer) => {
if(layer instanceof GenericMarker) {
(layer as GenericMarker).createLabel();
}
});
}
this.options.showLabels = options.showLabels;
}
if(this._markerPane) { if(this._markerPane) {
this._markerPane.classList.toggle('leaflet-pane--show-labels', options.showLabels); this._markerPane.classList.toggle('leaflet-pane--show-labels', options.showLabels);
@ -128,7 +141,7 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
} }
} }
_updateLayerVisibility(onAdd?: boolean) { private _updateLayerVisibility(onAdd?: boolean) {
if(!this._map) { if(!this._map) {
return; return;
} }
@ -142,34 +155,34 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
this.eachLayer((layer) => { this.eachLayer((layer) => {
//Per marker zoom limits take precedence, if present //Per marker zoom limits take precedence, if present
if(this._zoomLimitedLayers.has(layer)) { if(this._zoomLimitedLayers.has(layer)) {
this._isLayerVisible(layer, zoom) ? this._map.addLayer(layer) : this._map.removeLayer(layer); LiveAtlasLayerGroup._isLayerVisible(layer, zoom) ? this._addToMap(layer) : this._removeFromMap(layer);
} else { //Otherwise apply group zoom limit } else { //Otherwise apply group zoom limit
visible ? this._map.addLayer(layer) : this._map.removeLayer(layer); visible ? this._addToMap(layer) : this._removeFromMap(layer);
} }
}, this); }, this);
//Group isn't zoom limited, but some individual markers are //Group isn't zoom limited, but some individual markers are
} else if(this._zoomLimitedLayers.size) { } else if(this._zoomLimitedLayers.size) {
this._zoomLimitedLayers.forEach((layer) => { this._zoomLimitedLayers.forEach((layer) => {
this._isLayerVisible(layer, zoom) ? this._map.addLayer(layer) : this._map.removeLayer(layer); LiveAtlasLayerGroup._isLayerVisible(layer, zoom) ? this._addToMap(layer) : this._removeFromMap(layer);
}); });
//Nothing is zoom limited, but we've just been added to the map //Nothing is zoom limited, but we've just been added to the map
} else if(onAdd) { } else if(onAdd) {
this.eachLayer(this._map.addLayer, this._map); this.eachLayer((layer: Layer) => this._addToMap(layer), this._map);
} }
} }
//Returns if this layer group has zoom limits defined //Returns if this layer group has zoom limits defined
_isZoomLimited() { private _isZoomLimited() {
return this.options.maxZoom !== undefined || this.options.minZoom !== undefined; return this.options.maxZoom !== undefined || this.options.minZoom !== undefined;
} }
//Returns if the given layer has its own zoom limits defined //Returns if the given layer has its own zoom limits defined
_isLayerZoomLimited(layer: Layer) { private static _isLayerZoomLimited(layer: Layer) {
return ((layer as any).options && (layer as any).options.minZoom !== undefined) return ((layer as any).options && (layer as any).options.minZoom !== undefined)
&& ((layer as any).options && (layer as any).options.maxZoom !== undefined); && ((layer as any).options && (layer as any).options.maxZoom !== undefined);
} }
_isLayerVisible(layer: Layer, currentZoom: number) { private static _isLayerVisible(layer: Layer, currentZoom: number) {
let minZoom = -Infinity, let minZoom = -Infinity,
maxZoom = Infinity; maxZoom = Infinity;
@ -183,4 +196,17 @@ export default class LiveAtlasLayerGroup extends LayerGroup {
return currentZoom >= minZoom && currentZoom <= maxZoom; return currentZoom >= minZoom && currentZoom <= maxZoom;
} }
private _addToMap(layer: Layer) {
this._map.addLayer(layer)
//Create marker label immediately if labels are visible by default
if(layer instanceof GenericMarker && this.options.showLabels) {
(layer as GenericMarker).createLabel();
}
}
private _removeFromMap(layer: Layer) {
this._map.removeLayer(layer)
}
} }

View File

@ -15,16 +15,30 @@
*/ */
import {MarkerOptions, Marker, Util, LatLngExpression, Icon} from 'leaflet'; import {MarkerOptions, Marker, Util, LatLngExpression, Icon} from 'leaflet';
import {LiveAtlasMarker} from "@/index";
import {GenericIcon} from "@/leaflet/icon/GenericIcon";
export interface GenericMarkerOptions extends MarkerOptions { export interface GenericMarkerOptions extends MarkerOptions {
icon: GenericIcon;
minZoom?: number; minZoom?: number;
maxZoom?: number; maxZoom?: number;
} }
export class GenericMarker extends Marker { export class GenericMarker extends Marker {
constructor(latLng: LatLngExpression, options: GenericMarkerOptions) { declare options: GenericMarkerOptions;
super(latLng, options);
Util.setOptions(this, options); constructor(latLng: LatLngExpression, options: LiveAtlasMarker) {
super(latLng, {});
this.options.icon = new GenericIcon({
icon: options.icon,
label: options.label,
iconSize: options.dimensions,
isHtml: options.isLabelHTML,
});
this.options.maxZoom = options.maxZoom;
this.options.minZoom = options.maxZoom;
} }
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
@ -35,4 +49,8 @@ export class GenericMarker extends Marker {
getIcon(): Icon.Default { getIcon(): Icon.Default {
return this.options.icon as Icon.Default; return this.options.icon as Icon.Default;
} }
createLabel(): void {
this.options.icon.createLabel();
}
} }

View File

@ -23,16 +23,7 @@ import {GenericMarker} from "@/leaflet/marker/GenericMarker";
import {LiveAtlasMarker} from "@/index"; import {LiveAtlasMarker} from "@/index";
export const createMarker = (options: LiveAtlasMarker, converter: Function): Marker => { export const createMarker = (options: LiveAtlasMarker, converter: Function): Marker => {
const marker = new GenericMarker(converter(options.location), { const marker = new GenericMarker(converter(options.location), options);
icon: new GenericIcon({
icon: options.icon,
label: options.label,
iconSize: options.dimensions,
isHtml: options.isLabelHTML,
}),
maxZoom: options.maxZoom,
minZoom: options.minZoom,
});
marker.on('click', (e: LeafletMouseEvent) => { marker.on('click', (e: LeafletMouseEvent) => {
e.target._map.panTo(e.target.getLatLng()); e.target._map.panTo(e.target.getLatLng());