HERE Technologies использует в JavaScript API гибридную модель рендеринга, где карта состоит из базового слоя тайлов, векторных/растровых данных и динамических объектов (маркерных слоёв, полигонов, линий и UI-оверлеев). Любое изменение состояния карты может приводить к частичной или полной перерисовке сценического графа.
Ключевые источники перерисовок:
setCenter)setZoom)H.map.Object)H.ui)Минимизация перерисовок опирается на контроль этих триггеров и сокращение количества операций, влияющих на сцену.
Любая последовательность операций над картой должна рассматриваться как единая транзакция. Частые одиночные вызовы приводят к каскадному пересчёту сцены.
Проблемный паттерн:
map.setCenter({ lat: 52.5, lng: 13.4 });
map.setZoom(10);
map.addObject(marker);
Каждая операция вызывает отдельный цикл обновления.
Оптимизированный подход — агрегирование состояния:
map.getViewModel().setLookAtData({
position: { lat: 52.5, lng: 13.4 },
zoom: 10
});
map.addObject(marker);
Идея заключается в уменьшении количества пересчётов камеры и viewport.
Любые динамические изменения объектов должны быть синхронизированы с циклом рендеринга браузера.
Антипаттерн:
points.forEach(p => {
marker.setGeometry(p);
});
Оптимизация через кадрирование:
requestAnimationFrame(() => {
const batch = [];
points.forEach(p => batch.push(p));
batch.forEach(p => marker.setGeometry(p));
});
При большом количестве объектов дополнительно применяется разбиение на чанки:
function updateInChunks(data, chunkSize = 50) {
let index = 0;
function step() {
const end = Math.min(index + chunkSize, data.length);
for (let i = index; i < end; i++) {
markers[i].setGeometry(data[i]);
}
index = end;
if (index < data.length) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}
Объекты H.map.Marker являются частым источником
нагрузки. Каждое изменение геометрии или иконки может инициировать
перерасчёт слоя.
Создание новых маркеров дорогостояще. Вместо этого используется пул объектов:
const pool = [];
function getMarker() {
return pool.pop() || new H.map.Marker({ lat: 0, lng: 0 });
}
function releaseMarker(marker) {
marker.setGeometry({ lat: 0, lng: 0 });
pool.push(marker);
}
Это снижает давление на garbage collector и уменьшает количество DOM/WebGL операций.
События движения карты генерируются с высокой частотой, особенно при
drag и pointermove.
Антипаттерн:
map.addEventListener('pointermove', e => {
updateUI(e.currentPointer);
});
Оптимизация через throttle:
function throttle(fn, delay) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= delay) {
last = now;
fn(...args);
}
};
}
map.addEventListener('pointermove', throttle(e => {
updateUI(e.currentPointer);
}, 50));
Debounce применяется для событий завершения взаимодействия:
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
Каждый H.map.Object добавленный отдельно увеличивает
нагрузку на сцену. Вместо множества независимых объектов применяется
группировка:
const group = new H.map.Group();
markers.forEach(m => group.addObject(m));
map.addObject(group);
Группы уменьшают количество пересчётов hit-testing и рендеринга.
Изменение геометрии (setGeometry) является дорогостоящей
операцией. Векторные объекты пересчитываются полностью.
Оптимизация:
function updateMarker(marker, coords) {
const current = marker.getGeometry();
if (current.lat !== coords.lat || current.lng !== coords.lng) {
marker.setGeometry(coords);
}
}
Изменения viewport через H.map.ViewPort или изменение
размеров контейнера вызывают полный пересчёт сцены.
При динамическом UI важно минимизировать resize-события:
const resizeObserver = new ResizeObserver(() => {
map.getViewPort().resize();
});
resizeObserver.observe(document.getElementById('mapContainer'));
Оптимизация заключается в предотвращении частых вызовов resize через debounce.
HERE Maps API предоставляет события разного уровня частоты:
mapviewchange — высокая частотаmapviewchangeend — завершение движенияpointermove — крайне частоеОптимальная стратегия:
mapviewchangeendmap.addEventListener('mapviewchangeend', () => {
updateVisibleObjects();
});
Каждый слой увеличивает нагрузку на сцену, особенно при пересечении прозрачных объектов.
Оптимизация:
H.map.GroupUI слой H.ui.UI может генерировать дополнительные
перерисовки при включённых контролах:
Минимизация:
Добавление множества объектов по одному вызывает серию рендеров.
Антипаттерн:
data.forEach(d => map.addObject(new H.map.Marker(d)));
Оптимизация:
const group = new H.map.Group();
data.forEach(d => {
group.addObject(new H.map.Marker(d));
});
map.addObject(group);
Hit-testing выполняется при каждом pointer-событии. Большое количество интерактивных объектов увеличивает нагрузку.
Подходы:
setInteractive(true)marker.setInteractive(false);
Динамическое создание иконок вызывает повторные GPU/DOM операции.
Оптимизация:
H.map.Iconconst icon = new H.map.Icon('/img/marker.png');
const marker = new H.map.Marker(coords, { icon });
Частая ошибка — связывание состояния приложения с картой напрямую, что приводит к цепным обновлениям.
Проблемный паттерн:
state.onChange(() => {
map.setCenter(state.center);
});
Оптимизация:
Удаление объектов должно сопровождаться явным освобождением ссылок:
map.removeObject(marker);
marker.dispose?.();
marker = null;
При массовых изменениях предпочтительно очищать группы:
group.removeAll();
При большом количестве объектов применяется адаптивная детализация:
Это напрямую уменьшает число перерисовок сцены.
При тысячах объектов рендер каждого маркера становится неоптимальным. Кластеризация объединяет их в агрегированные сущности, снижая количество draw calls и операций hit-testing.
const clusterProvider = new H.clustering.Provider(dataPoints);
const layer = new H.map.layer.ObjectLayer(clusterProvider);
map.addLayer(layer);
Любые изменения должны учитываться в контексте кадра карты. Частые вызовы API вне синхронизации с render loop приводят к избыточным пересчётам сцены.
Оптимальный подход — централизованная очередь изменений:
const queue = [];
function scheduleUpdate(fn) {
queue.push(fn);
}
function flush() {
const batch = queue.splice(0);
batch.forEach(fn => fn());
requestAnimationFrame(flush);
}
requestAnimationFrame(flush);