Работа с маркерами в Leaflet строится вокруг преобразования географических координат в пиксельные координаты слоя карты и обратно. Маркер существует одновременно в двух представлениях: как географическая сущность (широта и долгота) и как DOM-объект, размещённый в контейнере карты. Понимание механики позиционирования позволяет управлять точностью отображения, корректно выравнивать иконки и предсказуемо работать с динамическими изменениями карты.
Каждый маркер задаётся в виде пары координат:
L.marker([55.751244, 37.618423]).addTo(map);
Эти значения интерпретируются в системе координат карты (обычно WGS84), а затем преобразуются в пиксели текущего viewport.
Ключевой момент: Leaflet не хранит маркеры в пикселях — он хранит их в географических координатах и пересчитывает позицию при каждом изменении состояния карты.
Любое движение карты (панорамирование, масштабирование, изменение CRS) приводит к перерасчёту позиции всех маркеров.
Базовый метод изменения положения маркера:
const marker = L.marker([55.75, 37.61]).addTo(map);
marker.setLatLng([55.76, 37.62]);
При вызове setLatLng происходит:
Этот механизм используется при анимациях, трекинге объектов и обновлении данных в реальном времени.
Маркер визуально представляет собой иконку, у которой есть точка привязки. По умолчанию Leaflet центрирует нижнюю часть иконки в заданных координатах, но это поведение часто требует корректировки.
const icon = L.icon({
iconUrl: 'marker.png',
iconSize: [32, 32],
iconAnchor: [16, 32]
});
L.marker([55.75, 37.61], { icon }).addTo(map);
[0, 0] — левый верхний угол изображения[width/2, height] — классическая нижняя центральная
точкаВажно: именно iconAnchor, а не
географические координаты определяют, какая часть изображения «прибита»
к карте.
Попапы привязываются относительно маркера, а не координат карты:
const icon = L.icon({
iconUrl: 'marker.png',
iconSize: [32, 32],
iconAnchor: [16, 32],
popupAnchor: [0, -32]
});
Значения popupAnchor определяют смещение относительно
iconAnchor.
Типичные сценарии:
[0, -height][width / 2, 0]Leaflet использует слой Pane, внутри которого каждый
маркер размещается как DOM-элемент с абсолютным позиционированием.
При каждом изменении карты происходит:
transform: translate(...)Пример упрощённой логики:
const point = map.latLngToLayerPoint([lat, lng]);
markerEl.style.transform = `translate(${point.x}px, ${point.y}px)`;
Это объясняет высокую производительность: браузер оптимизирует только трансформации, а не полную переразметку DOM.
Часто требуется переместить карту так, чтобы маркер оказался в центре или в заданной области:
map.setView([55.75, 37.61], 13);
или более гибко:
map.panTo([55.75, 37.61]);
Разница:
setView — меняет и центр, и масштабpanTo — изменяет только позициюПри этом маркер автоматически пересчитывает своё положение, так как зависит от текущего viewport.
Маркер может быть перетаскиваемым:
const marker = L.marker([55.75, 37.61], {
draggable: true
}).addTo(map);
marker.on('drag', (e) => {
const pos = e.target.getLatLng();
});
Во время drag:
dragЭто важное отличие: движение маркера не обязательно означает изменение карты.
Каждый zoom вызывает перерасчёт всех точек:
map.on('zoomend', () => {
console.log('zoom changed');
});
Маркер при этом не меняет lat/lng, меняется только визуальное положение.
При работе с нестандартными иконками (стрелки, треугольники, SVG) важно правильно задать якорь:
const icon = L.divIcon({
html: '<div class="arrow-marker"></div>',
iconSize: [20, 20],
iconAnchor: [10, 10]
});
Ошибки в iconAnchor приводят к смещению:
В основе позиционирования лежит проекция карты (CRS — Coordinate Reference System).
По умолчанию используется:
Функции преобразования:
map.project([lat, lng], zoom);
map.unproject(point, zoom);
Именно через них определяется точное положение маркера в пикселях.
Если контейнер карты изменяет размеры или положение в DOM, Leaflet автоматически пересчитывает позиции:
map.invalidateSize();
Без этого вызова маркеры могут:
При обновлении координат из внешнего источника:
setInterval(() => {
marker.setLatLng([
55.75 + Math.random() * 0.01,
37.61 + Math.random() * 0.01
]);
}, 1000);
Позиция обновляется без пересоздания объекта, что снижает нагрузку на DOM и ускоряет рендеринг.
Маркер может находиться в разных pane:
const marker = L.marker([55.75, 37.61], {
pane: 'overlayPane'
});
Каждый pane имеет свой z-index и правила трансформации, влияющие на визуальное наложение объектов.
Позиционирование маркеров тесно связано с событиями:
movezoomresizeviewresetПример реакции на движение карты:
map.on('move', () => {
// маркеры автоматически перерасчитываются
});
Хотя явного обновления не требуется, события позволяют синхронизировать внешние слои и кастомные оверлеи.
Внутренний цикл работы маркера можно описать так:
Эта цепочка выполняется при каждом изменении состояния карты или самого маркера, обеспечивая стабильное и предсказуемое позиционирование в любых сценариях взаимодействия.