Best practices
This article outlines a number of best practices aiming to help you use the HERE Maps API for JavaScript in the most efficient way regarding optimal map responsiveness.
Icon reuse
In Maps API for JavaScript, the H.map.Icon
class represents a reusable visual map marker. To optimize map performance, reuse an icon instance in as many marker objects as possible instead of creating a new H.map.Icon
instance for each marker.
The code below represents the non-recommended method of creating a new icon
object for each new marker:
var points = [...],
markers = [];
for (var i = 0; i < points.length; i++) {
markers.push(new H.map.Marker(points[i], {
icon: new H.map.Icon('graphics/markerHouse.png')
}));
}
Instead of creating a new icon
object for each marker, reuse the single H.map.Icon
instance across all H.map.Marker
instances as shown below:
var points = [...],
markers = [],
icon = new H.map.Icon('graphics/markerHouse.png');
for (var i = 0; i < points.length; i++) {
markers.push(new H.map.Marker(points[i], {
icon: icon
}));
}
The approach demonstrated above allows achieving better performance and a lower memory footprint.
Note
When you need to display a large number of map markers, consider marker clustering.
Keep "onAttach" and "onDetach" callbacks simple
When using DomMarker
, you may wish to pass onAttach
and onDetach
options to the DomIcon
constructor to add event listeners for all DomMarkers
using the DomIcon
instance.
Note that those callbacks are called each time the DomMarker
appears on the map or disappears from the map viewport. For this reason, we recommend not to perform any expensive computations in those callbacks, otherwise, map panning and zooming performance may noticeably degrade.
The example below shows the non-recommended approach that causes such performance degradation:
var domIcon = new H.map.DomIcon(domElement, {
onAttach: function(clonedElement, domIcon, domMarker) {
var defaultButton = clonedElement.querySelector('.btn-default');
var infoButton = clonedElement.querySelector('.btn-info');
defaultButton.addEventListener('click', onDefaultClick);
infoButton.addEventListener('click', onInfoClick);
},
onDetach: function(clonedElement, domIcon, domMarker) {
var defaultButton = clonedElement.querySelector('.btn-default');
var infoButton = clonedElement.querySelector('.btn-info');
defaultButton.removeEventListener('click', onDefaultClick);
infoButton.removeEventListener('click', onInfoClick);
}
});
Make sure you compute all the required assets beforehand or offload the computation from onAttach()/onDetach()
to event callbacks by using techniques such as event delegation demonstrated in the code below:
function onMarkerClick(e) {
var targetClassName = e.target.className;
if (targetClassName === 'btn-default') {
onDefaultClick(e);
} else if (targetClassName === 'btn-info') {
onInfoClick(e);
}
}
var domIcon = new H.map.DomIcon(domElement, {
onAttach: function(clonedElement, domIcon, domMarker) {
domElement.addEventListener('click', onMarkerClick);
},
onDetach: function(clonedElement, domIcon, domMarker) {
domElement.removeEventListener('click', onMarkerClick);
}
});
Use "mapviewchangeend" event rather than "mapviewchange"
The events mapviewchange
and mapviewchangeend
seem similar – both can be used for handling changes to the map viewport – but their effect is different. mapviewchange
is called on each animation step in the process of changing a state of the viewport. In many cases, this operation is excessive. For example, updating a UI element with the current zoom level multiple times each time the user double-clicks on the map is inefficient.
function displayZoomLevel() {...}
map.addEventListener('mapviewchange', function () {
var zoom = map.getZoom();
displayZoomLevel(zoom);
});
mapviewchangeend
, on the other hand, is called only once, when the viewport change is complete, therefore, we recommend using this event to update the map viewport:
function displayZoomLevel() {...}
map.addEventListener('mapviewchangeend', function () {
var zoom = map.getZoom();
displayZoomLevel(zoom);
});
The mapviewchangeend
event is a 'debounced' version of mapviewchange
, so, as a rule, it is more efficient to use mapviewchangeend
.
Use built-in animation capabilities
If you need to animate a transition between different map states, for example, a transition resulting from a map center change, a zoom change or both, always try to use built-in animation capabilities of the map engine.
Map instance methods such as setZoom()
, setCenter()
and setViewBounds()
accept a second argument indicating whether an animated transition should be applied.
We recommend not to create your own zoom animations as shown in the snippet below:
var currentZoom = map.getZoom();
var endZoom = currentZoom + 3;
function step() {
currentZoom += 0.05;
map.setZoom(currentZoom);
(currentZoom < endZoom) && requestAnimationFrame(step);
}
step();
Instead, call the method setZoom()
with an optional second parameter to perform an animation:
map.setZoom(map.getZoom() + 3, true);
Use "SpatialStyle" instances as read-only objects
Instances of SpatialStyle
are intended to be used as read-only objects. Modifying their properties is not safe and may lead to visual inconsistencies.
The snippet below shows an example of reusing SpatialStyle
of one Spatial
object on another. This operation is valid as it does not modify the SpatialStyle
object.
var style = spatial1.setStyle();
spatial2.setStyle(style);
If you want to reuse a style from one Spatial
object on another, but with a single fillColor
modification, modifying an object returned by getStyle
method can have non-deterministic result on a visual representation of the first spatial:
var style = spatial1.getStyle();
style.fillColor = 'red';
spatial2.setStyle(style);
The correct way to change the style is to create a new SpatialStyle
object or to use the getCopy()
method on an existing object as shown in the following code example:
var style = spatial1.setStyle().getCopy({
fillColor: 'red'
});
spatial2.setStyle(style);
Thus, the general rule is to reuse an existing SpatialStyle
object multiple times, but if you need to modify it, use getCopy()
or construct a new one.