Производительность в Leaflet определяется не только количеством объектов на карте, но и тем, как именно эти объекты создаются, рендерятся и обновляются в DOM. Архитектура библиотеки построена вокруг DOM-слоёв (SVG, Canvas, HTML-элементы), что делает её гибкой, но накладывает ограничения при работе с большими объёмами данных.
Ключевые узлы, влияющие на производительность:
Leaflet по умолчанию использует DOM-элементы для отображения объектов. Каждый маркер — это отдельный HTML-элемент, что при тысячах объектов приводит к резкому падению производительности.
Проблемные сценарии:
DOM-операции являются дорогими: браузер вынужден пересчитывать layout, repaint и composite на каждом изменении.
Leaflet поддерживает два основных подхода к отрисовке векторных слоёв.
SVG используется по умолчанию для векторных объектов.
Особенности:
Подходит для:
Canvas снижает нагрузку за счёт рисования в одном элементе.
Инициализация:
const map = L.map('map', {
preferCanvas: true
});
или для отдельных слоёв:
const canvasRenderer = L.canvas();
const circle = L.circle([51.5, -0.09], {
radius: 200,
renderer: canvasRenderer
}).addTo(map);
Преимущества:
Недостатки:
Стандартные маркеры — один из самых дорогих элементов Leaflet.
Причины:
Даже 2 000 маркеров могут вызывать заметные лаги при анимации карты.
Основной способ оптимизации — агрегация точек.
Популярная стратегия — использование
Leaflet.markercluster.
const markers = L.markerClusterGroup();
for (let i = 0; i < 10000; i++) {
const marker = L.marker([
51.5 + Math.random(),
-0.09 + Math.random()
]);
markers.addLayer(marker);
}
map.addLayer(markers);
Принцип работы:
Эффект:
L.tileLayer отвечает за загрузку растровых тайлов. Здесь
производительность зависит от сети и кеширования.
Ключевые параметры:
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
updateWhenZooming: false,
keepBuffer: 2,
crossOrigin: true
}).addTo(map);
Отключение обновления тайлов во время зума снижает нагрузку на сеть и CPU.
Контролирует количество тайлов за пределами видимой области, влияя на плавность панорамирования.
Каждое добавление слоя в карту вызывает перерасчёт.
Проблема:
for (let i = 0; i < 1000; i++) {
L.marker([lat, lng]).addTo(map);
}
Оптимизированный подход:
const layerGroup = L.layerGroup();
for (let i = 0; i < 1000; i++) {
layerGroup.addLayer(L.marker([lat, lng]));
}
layerGroup.addTo(map);
Разница:
При динамических данных (например, трекинг объектов) критично ограничивать частоту обновлений.
Пример проблемы:
Решение — throttle:
function throttle(fn, limit) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn.apply(this, args);
}
};
}
const updateMarkers = throttle(() => {
markers.forEach(m => m.setLatLng(getNewPosition(m)));
}, 100);
Эффект:
Сложные полигоны с тысячами точек существенно нагружают браузер.
Проблемы:
Оптимизации:
Алгоритмы упрощения (например, Douglas–Peucker) уменьшают количество точек без значительной потери формы.
Пример подхода:
Это снижает:
Основные инструменты:
Метрики, которые важны:
Типичная проблема:
Leaflet генерирует множество событий:
movezoomresizeЧастые ошибки:
moveОптимизация:
map.on('move', throttle(() => {
updateVisibleObjects();
}, 50));
При работе с большим количеством объектов важно отображать только то, что попадает в viewport.
Принцип:
function getVisiblePoints(points, bounds) {
return points.filter(p => bounds.contains([p.lat, p.lng]));
}
Это уменьшает:
Leaflet может создавать утечки при неправильном удалении слоёв.
Типичные причины:
Правильное удаление:
map.removeLayer(layer);
layer.off();
layer = null;
При росте объёма данных важно переходить от прямого добавления объектов к структурированным подходам: