Перерисовки карты в Google Maps JavaScript API становятся основным источником деградации производительности при работе с большим количеством объектов, динамическими обновлениями состояния и частыми изменениями пользовательского интерфейса. Минимизация перерисовок сводится к контролю жизненного цикла карты, снижению числа операций, влияющих на DOM и WebGL-контекст, а также к переиспользованию уже созданных сущностей вместо их пересоздания.
Каждое изменение состояния карты потенциально вызывает перерасчёт:
Наиболее дорогими операциями являются:
setOptions с множеством параметровКлючевой принцип оптимизации — избегать уничтожения и пересоздания карты и объектов, если достаточно изменить их состояние.
Создание карты должно происходить один раз на жизненный цикл приложения. Любая логика обновления данных не должна затрагивать конструктор карты.
const map = new google.maps.Map(document.getElementById("map"), {
center: { lat: 50.45, lng: 30.52 },
zoom: 10,
});
Дальнейшие изменения выполняются через методы:
map.setCenter({ lat: 50.46, lng: 30.60 });
map.setZoom(12);
Избыточная практика — пересоздание карты при каждом изменении данных — приводит к полной перерисовке всех слоёв и сбросу состояния.
Частые события (scroll, mousemove, input) могут вызывать обновления карты десятки раз в секунду. Без ограничения частоты вызовов возникает эффект постоянной перерисовки.
Используются техники:
function throttle(fn, delay) {
let last = 0;
return (...args) => {
const now = Date.now();
if (now - last >= delay) {
last = now;
fn(...args);
}
};
}
window.addEventListener("mousemove",
throttle((e) => {
map.setCenter({ lat: e.clientY * 0.001, lng: e.clientX * 0.001 });
}, 50)
);
Главный эффект — уменьшение количества трансформаций viewport и пересчётов координат.
Маркеры являются частым источником перерисовок, особенно при их массовом создании и удалении.
Антипаттерн:
markers.forEach(m => m.setMap(null));
markers = [];
markers.forEach(data => {
const marker = new google.maps.Marker({
position: data.position,
map
});
markers.push(marker);
});
Каждый setMap(null) и повторное создание маркера
инициирует перерасчёт слоя.
Оптимальный подход — обновление существующих экземпляров:
markers.forEach((marker, i) => {
marker.setPosition(data[i].position);
});
При необходимости добавления и удаления используется пул объектов, а не пересоздание массива.
При большом количестве объектов (сотни и тысячи точек) критично снижать нагрузку на рендеринг.
Кластеризация объединяет маркеры в группы, уменьшая количество DOM/overlay элементов.
const clusterer = new markerClusterer.MarkerClusterer({
map,
markers
});
С точки зрения перерисовок это снижает:
Частые вызовы:
setCenterpanTofitBoundsмогут приводить к каскадным перерисовкам.
Особенно дорог fitBounds, так как он:
Оптимизация заключается в группировке изменений:
map.setOptions({
center: newCenter,
zoom: newZoom
});
Единичный вызов снижает количество внутренних перерасчётов.
setOptions пересоздаёт внутренние конфигурации карты и
может вызывать лишние обновления.
Антипаттерн:
map.setOptions({ zoom: map.getZoom() + 1 });
map.setOptions({ disableDefaultUI: true });
map.setOptions({ draggable: false });
Каждый вызов потенциально запускает перерасчёт рендера.
Правильный подход — группировка изменений:
map.setOptions({
zoom: map.getZoom() + 1,
disableDefaultUI: true,
draggable: false
});
Кастомные overlay (например, OverlayView) часто
создаются заново при обновлении данных, что приводит к полной
перерисовке DOM-слоя.
Оптимизация:
setMap(map)class CustomOverlay extends google.maps.OverlayView {
draw() {
const projection = this.getProjection();
const pos = projection.fromLatLngToDivPixel(this.position);
this.div.style.left = pos.x + "px";
this.div.style.top = pos.y + "px";
}
updatePosition(position) {
this.position = position;
this.draw();
}
}
Ключевой момент — draw() должен работать только с
изменёнными данными, без пересоздания DOM.
События bounds_changed, zoom_changed,
center_changed могут генерироваться с высокой частотой.
Антипаттерн:
map.addListener("bounds_changed", () => {
updateMarkers();
});
Это приводит к множественным обновлениям при одном жесте пользователя.
Оптимизация — привязка к стабильным событиям:
idle вместо bounds_changedmap.addListener("idle", () => {
updateMarkers();
});
Современный подход предполагает использование
AdvancedMarkerElement, который более эффективно управляет
DOM-структурой и снижает стоимость обновлений.
Преимущества:
const marker = new google.maps.marker.AdvancedMarkerElement({
map,
position: { lat: 50.45, lng: 30.52 },
});
Обновление позиции не требует пересоздания элемента, что уменьшает число перерисовок.
Частая ошибка — выполнение тяжёлых вычислений внутри событий карты.
Антипаттерн:
map.addListener("bounds_changed", () => {
heavyGeoCalculation();
});
Это блокирует рендеринг и увеличивает задержки перерисовки.
Оптимальный подход:
При интеграции с React, Vue или аналогичными системами частая причина перерисовок — изменение ссылок на массивы и объекты.
Антипаттерн:
setMarkers([...newMarkers]);
Каждое обновление создаёт новый массив и вызывает полную пересборку маркеров.
Оптимизация — мутирование существующих структур или диффинг:
DOM-элементы внутри карты должны обновляться точечно:
style.transform вместо пересоздания
элементаlabel.textContent = newValue;
markerDiv.style.transform = `translate(${x}px, ${y}px)`;
Любая замена DOM-узла приводит к повторной компоновке слоя карты.
При потоковых данных границы часто пересчитываются многократно.
Решение — накопление точек и редкое обновление:
let bounds = new google.maps.LatLngBounds();
function addPoint(p) {
bounds.extend(p);
}
setInterval(() => {
map.fitBounds(bounds);
}, 1000);
Это уменьшает количество перерасчётов zoom и tile layout.
При работе с проекцией координат:
const projection = overlay.getProjection();
повторные вызовы fromLatLngToDivPixel можно кешировать
при неизменном zoom и center.
Стратегия:
zoom_changedЭто снижает нагрузку на вычисление экранных координат и уменьшает количество визуальных обновлений overlay-слоя.