Перетаскивание маркеров

Leaflet поддерживает интерактивное перемещение объектов карты через механизм drag-and-drop, встроенный в слой маркеров. Перетаскивание маркеров является одним из ключевых инструментов построения интерактивных картографических интерфейсов: оно позволяет пользователю изменять географическое положение объектов в реальном времени и синхронизировать эти изменения с бизнес-логикой приложения.

По умолчанию маркеры в Leaflet могут быть перетаскиваемыми только при явном включении соответствующего режима. Основной параметр задаётся при создании маркера:

const marker = L.marker([51.505, -0.09], {
  draggable: true
}).addTo(map);

Ключевое свойство draggable: true активирует обработчики событий мыши и сенсорного ввода, позволяя изменять координаты маркера непосредственно на карте.

Альтернативный способ — включение перетаскивания после создания объекта:

marker.dragging.enable();

Отключение выполняется аналогично:

marker.dragging.disable();

Такой подход используется в сценариях, где режим редактирования карты переключается динамически.

Основные события перетаскивания

Механизм drag в Leaflet построен на событийной модели. При перемещении маркера генерируются следующие события:

  • dragstart — начало перетаскивания
  • drag — процесс перемещения
  • dragend — завершение перемещения

Пример подписки:

marker.on('dragstart', function () {
  console.log('Перетаскивание началось');
});

marker.on('drag', function () {
  const position = marker.getLatLng();
  console.log('Текущие координаты:', position);
});

marker.on('dragend', function () {
  const finalPosition = marker.getLatLng();
  console.log('Финальные координаты:', finalPosition);
});

События позволяют синхронизировать состояние маркера с сервером, формами или другими слоями карты.

Получение и обновление координат

Во время и после перетаскивания координаты маркера извлекаются методом getLatLng():

const coords = marker.getLatLng();
console.log(coords.lat, coords.lng);

Для программного изменения позиции используется setLatLng():

marker.setLatLng([51.51, -0.1]);

Этот метод часто применяется после валидации пользовательского ввода или при восстановлении состояния карты.

Центрирование карты при перетаскивании

При перемещении маркера за пределы текущего viewport карта может оставаться статичной. Для автоматического следования за маркером используется обработка события drag:

marker.on('drag', function () {
  map.panTo(marker.getLatLng());
});

Такой подход полезен в интерфейсах, где маркер представляет активную сущность (например, курьер или транспорт).

Более плавный вариант:

map.panTo(marker.getLatLng(), { animate: true });

Ограничение области перетаскивания

Leaflet позволяет ограничивать перемещение маркеров географическими границами. Это реализуется через проверку координат в обработчике события:

const bounds = L.latLngBounds(
  L.latLng(51.49, -0.08),
  L.latLng(51.52, -0.06)
);

marker.on('drag', function () {
  const pos = marker.getLatLng();

  if (!bounds.contains(pos)) {
    marker.setLatLng(bounds.getCenter());
  }
});

Более мягкий вариант — «возврат» маркера в допустимую область после завершения перетаскивания:

marker.on('dragend', function () {
  const pos = marker.getLatLng();

  if (!bounds.contains(pos)) {
    marker.setLatLng(bounds.getCenter());
  }
});

Привязка маркера к сетке (snapping)

В задачах GIS и логистики часто требуется привязка маркера к дискретной сетке координат. Это реализуется округлением значений:

function snap(value, step) {
  return Math.round(value / step) * step;
}

marker.on('drag', function () {
  const pos = marker.getLatLng();

  const snapped = L.latLng(
    snap(pos.lat, 0.001),
    snap(pos.lng, 0.001)
  );

  marker.setLatLng(snapped);
});

Такой механизм уменьшает шум координат и обеспечивает стандартизированное позиционирование.

Обновление связанных объектов

Перетаскивание маркера часто используется вместе с обновлением других элементов интерфейса, например линий или полигонов.

Пример обновления линии:

const polyline = L.polyline([
  [51.505, -0.09],
  [51.51, -0.1]
]).addTo(map);

marker.on('drag', function () {
  const pos = marker.getLatLng();

  const latlngs = polyline.getLatLngs();
  latlngs[0] = pos;

  polyline.setLatLngs(latlngs);
});

Это позволяет строить интерактивные редакторы маршрутов.

Работа с несколькими маркерами

При наличии группы перетаскиваемых маркеров важно централизовать обработку событий:

function onMarkerDrag(e) {
  const marker = e.target;
  const pos = marker.getLatLng();

  console.log('Маркер перемещён:', pos);
}

markers.forEach(m => {
  m.on('dragend', onMarkerDrag);
});

Такой подход упрощает масштабирование логики при большом количестве объектов.

Производительность при частых событиях drag

Событие drag может вызываться десятки раз в секунду. Для оптимизации вычислений используется throttling:

let lastUpdate = 0;

marker.on('drag', function () {
  const now = Date.now();

  if (now - lastUpdate < 50) return;
  lastUpdate = now;

  const pos = marker.getLatLng();
  console.log(pos);
});

Это снижает нагрузку на DOM и предотвращает лишние вычисления.

Синхронизация с сервером

В реальных приложениях изменение координат часто отправляется на сервер:

marker.on('dragend', async function () {
  const pos = marker.getLatLng();

  await fetch('/api/update-position', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      lat: pos.lat,
      lng: pos.lng
    })
  });
});

Отправка данных именно в dragend уменьшает количество сетевых запросов и повышает стабильность интерфейса.

Интеграция с всплывающими окнами

Перетаскивание может сопровождаться обновлением popup:

marker.bindPopup('Переместите маркер');

marker.on('drag', function () {
  const pos = marker.getLatLng();
  marker.setPopupContent(`lat: ${pos.lat.toFixed(5)}, lng: ${pos.lng.toFixed(5)}`);
});

Popup становится динамическим индикатором координат.

Комбинация с пользовательскими иконками

Перетаскивание не зависит от типа иконки:

const customIcon = L.icon({
  iconUrl: 'icon.png',
  iconSize: [32, 32]
});

const marker = L.marker([51.505, -0.09], {
  icon: customIcon,
  draggable: true
}).addTo(map);

Это позволяет создавать интерактивные слои с визуально сложными маркерами без потери функциональности drag.

Управление состоянием через внешние UI элементы

Маркер может управляться кнопками интерфейса:

document.querySelector('#enable').oncl ick = () => marker.dragging.enable();
document.querySelector('#disable').oncl ick = () => marker.dragging.disable();

Такой подход используется в режимах редактирования, где пользователь явно включает изменение геометрии объектов.

Использование вместе с координатными формами

Перетаскивание часто связывается с input-полями:

marker.on('drag', function () {
  const pos = marker.getLatLng();

  document.querySelector('#lat').value = pos.lat;
  document.querySelector('#lng').value = pos.lng;
});

Обратная синхронизация:

function updateMarker() {
  marker.setLatLng([
    parseFloat(document.querySelector('#lat').value),
    parseFloat(document.querySelector('#lng').value)
  ]);
}

Такая двусторонняя связь используется в административных панелях геоданных.