Apply best practices for efficient map rendering
This article provides guidelines on how to effectively utilize the HERE Maps API for JavaScript, focusing on optimizing map responsiveness and ensuring a seamless user experience.
Reuse marker icons
In the HERE 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 following example demonstrates the non-recommended approach of creating a new icon object for each new marker:
// Array of anchor points for markers
var points = [...],
markers = [];
for (var i = 0; i < points.length; i++) {
markers.push(new H.map.Marker(points[i], {
// A new identical Icon instance is created at each iteration
// of the loop - do not do this:
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 in the following example:
// Array of anchor points for markers
var points = [...],
markers = [],
// Create single Icon instance
icon = new H.map.Icon('graphics/markerHouse.png');
for (var i = 0; i < points.length; i++) {
markers.push(new H.map.Marker(points[i], {
// Reuse the Icon instance:
icon: icon
}));
}The approach demonstrated in the preceding example allows better performance and lowers memory consumption.
Note
When displaying a large number of map markers, consider using marker clustering.
Keep onAttach and onDetach callbacks simple
onAttach and onDetach callbacks simpleWhen using DomMarker, you can enhance its functionality by passing the onAttach and onDetach options to the DomIcon constructor, allowing you to add event listeners for all DomMarkers associated with a particular DomIcon instance.
These callbacks are triggered each time a DomMarker appears on the map or disappears from the viewport. To ensure optimal performance, do not perform computationally expensive operations in these callbacks, as they can negatively impact panning and zooming capabilities.
The following example shows the non-recommended approach that causes such performance degradation:
// Assume that domElement is an already defined DOM element
// and contains a large number of children:
var domIcon = new H.map.DomIcon(domElement, {
onAttach: function(clonedElement, domIcon, domMarker) {
// Avoid expensive computation inside onAttach
// Such as quering the DOM:
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) {
// Avoid expensive computation inside onDetach
// such as quering the DOM
var defaultButton = clonedElement.querySelector('.btn-default');
var infoButton = clonedElement.querySelector('.btn-info');
defaultButton.removeEventListener('click', onDefaultClick);
infoButton.removeEventListener('click', onInfoClick);
}
});To ensure efficient processing, compute all necessary assets ahead of time or offload computation from the onAttach() and onDetach() methods to event callback functions. One effective approach is event delegation, as illustrated in the code snippet that follows:
// Use event bubbling to handle all events on the parent node itself,
// and perform computations only when needed.
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) {
// Simply subscribe to the event:
domElement.addEventListener('click', onMarkerClick);
},
onDetach: function(clonedElement, domIcon, domMarker) {
// Simply unsubscribe from the event:
domElement.removeEventListener('click', onMarkerClick);
}
});Use mapviewchangeend event rather than mapviewchange
mapviewchangeend event rather than mapviewchangeThe mapviewchange and mapviewchangeend events share similar functionality, both being used to handle changes to the map viewport. However, they exhibit distinct behavior: mapviewchange is triggered at each animation step during a state change of the viewport, whereas mapviewchangeend marks the completion of this process.
While you can employ both events for handling viewport updates, mapviewchange might be excessive in many cases, such as updating a UI element with the current zoom level multiple times when the user double-clicks on the map, which is inefficient.
The following code snippets shows an example of such non-recommended configuration that causes inefficiency:
// Displays current zoom level to the end user:
function displayZoomLevel() {...}
// A listener updates the map zoom level on each map view change
// -- this occurs more than 20 times on a double-click on the map,
// inefficient and to be avoided:
map.addEventListener('mapviewchange', function () {
var zoom = map.getZoom();
// This function is called more than 20 times on a double-click on the map!
displayZoomLevel(zoom);
});The mapviewchangeend event is called only once, when the viewport change is complete. Use this event to update the map viewport at that time. The following snippet shows an example of how to adhere to this recommendation:
/*
Displays current zoom level to the end user
*/
function displayZoomLevel() {...}
// A listener updates the map zoom level -- it is called once when the map
// view change is complete.
map.addEventListener('mapviewchangeend', function () {
var zoom = map.getZoom();
// The function that displays the zoom level is called only once,
// after zoom level changed
displayZoomLevel(zoom);
});The mapviewchangeend event is a debounced variant of mapviewchange. As a general guideline, using mapviewchangeend can be more efficient than its counterpart.
Use built-in animation capabilities
To animate transitions between different map states, such as those resulting from changes to the map's center or zoom level, use the built-in animation capabilities of the map engine.
Methods like H.Map#setZoom, H.Map#setCenter, H.map.ViewModel#setLookAtData, accept a second argument that specifies whether an animated transition should be applied.
For more information, see H.map page in the API Reference.
Avoid creating your own zoom animations as illustrated by the following example:
// Assumption: 'map' is initialized and available.
var currentZoom = map.getZoom();
var endZoom = currentZoom + 3;
// Update zoom level on each animation frame,
// till we reach endZoom:
function step() {
currentZoom += 0.05;
map.setZoom(currentZoom);
(currentZoom < endZoom) && requestAnimationFrame(step);
}
// Start zoom animation
step();Instead, call the setZoom() method with an optional second parameter to perform an animation, as shown in the following example:
/**
* Assumption: 'map' is initialized and available
*/
// Call getZoom() with an optional second parameter,
// indicating that an animation is to be performed:
map.setZoom(map.getZoom() + 3, true);Use H.map.SpatialStyle instances as read-only objects
H.map.SpatialStyle instances as read-only objectsInstances of H.map.SpatialStyle are designed to be used as read-only objects. Modifying their properties can lead to visual inconsistencies and is therefore not recommended.
The following snippet illustrates a valid use case for reusing an existing SpatialStyle object across multiple Spatial objects. This operation does not modify the original SpatialStyle object.
// Assumption: 'spatial1' and 'spatial2' are initialized and available.
// Get current style of the first spatial:
var style = spatial1.setStyle();
// Reusing an existing SpatialStyle object is fine,
// because we are not modifying it:
spatial2.setStyle(style);When reusing a style from one Spatial object on another, but with a single fillColor modification, modifying an object returned by the getStyle method can produce non-deterministic results for the visual representation of the original spatial.
// Assumption: 'spatial1' and 'spatial2' are initialized and available.
// Get current style of the first spatial:
var style = spatial1.getStyle();
// Modification of a SpatialStyle object -- never do this
style.fillColor = 'red';
// If we now apply the modified style to the second spatial, the results may be
// inconsistent -- sometimes the fillColor change takes effect, but not always!
spatial2.setStyle(style);To change the style correctly, you can either create a new SpatialStyle object or utilize the getCopy() method on an existing object, as illustrated in the following code example:
/**
* Assumption: 'spatial1' and 'spatial2' are initialized and available.
*/
// Get the current style of the first spatial and get an extended version
// by calling getCopy() method on it
var style = spatial1.setStyle().getCopy({
fillColor: 'red'
});
// Now changing the style of the second class is completely safe and
// produces the expected results consistently:
spatial2.setStyle(style);When reusing the SpatialStyle object, follow the best practice of using it multiple times. However, if modification is required, utilize the getCopy() method to obtain a copy of the original object or construct a new instance.
Updated last month