Mapbox GL JS построена вокруг WebGL-рендеринга и модели объектов, в
которой маркеры, слои и источники данных обновляются в реальном времени.
Поддержка drag-and-drop для маркеров реализуется через встроенный
механизм Marker, позволяющий изменять географические
координаты объекта с последующим синхронным обновлением состояния
карты.
Основной способ включения перетаскивания — использование опции
draggable: true при создании маркера.
mapboxgl.accessToken = 'YOUR_MAPBOX_ACCESS_TOKEN';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v12',
center: [69.2401, 41.2995],
zoom: 10
});
const marker = new mapboxgl.Marker({
draggable: true
})
.setLngLat([69.2401, 41.2995])
.addTo(map);
Внутри Marker используется DOM-элемент, который получает
обработчики pointer-событий. При активации draggable
библиотека автоматически регистрирует события мыши и
touch-интерфейса.
После завершения перетаскивания координаты извлекаются через метод
getLngLat().
function onDragEnd() {
const lngLat = marker.getLngLat();
console.log('Longitude:', lngLat.lng);
console.log('Latitude:', lngLat.lat);
}
marker.on('dragend', onDragEnd);
Событие dragend фиксирует финальную позицию, тогда как
drag позволяет отслеживать непрерывное движение.
Механизм событий включает три основных состояния:
dragstart — начало перемещенияdrag — активное перемещениеdragend — завершение перемещенияmarker.on('dragstart', () => {
console.log('start dragging');
});
marker.on('drag', () => {
const pos = marker.getLngLat();
console.log(pos);
});
marker.on('dragend', () => {
const final = marker.getLngLat();
console.log('final position:', final);
});
При частом использовании события drag необходимо
учитывать нагрузку на главный поток, особенно при обновлении UI или
синхронизации с сервером.
В прикладных сценариях маркер часто связан с внешним состоянием (например, объектом в базе или GeoJSON-слоем). При перетаскивании обновляется источник данных.
const state = {
point: [69.2401, 41.2995]
};
marker.on('dragend', () => {
const { lng, lat } = marker.getLngLat();
state.point = [lng, lat];
});
Такой подход используется в системах редактирования геоданных, где карта выступает интерфейсом визуального редактирования координат.
При большом количестве объектов важно избегать создания избыточных замыканий и дублирования обработчиков.
const points = [
[69.2, 41.3],
[69.25, 41.31],
[69.28, 41.29]
];
const markers = points.map((coords) => {
const m = new mapboxgl.Marker({ draggable: true })
.setLngLat(coords)
.addTo(map);
m.on('dragend', () => {
const pos = m.getLngLat();
console.log('updated:', pos);
});
return m;
});
При масштабировании системы обработку событий часто централизуют через идентификаторы маркеров.
Маркер может содержать произвольный HTML-элемент, что позволяет создавать сложные интерфейсы.
const el = document.createElement('div');
el.className = 'custom-marker';
el.innerHTML = '<div class="pin"></div>';
const customMarker = new mapboxgl.Marker({
element: el,
draggable: true
})
.setLngLat([69.2401, 41.2995])
.addTo(map);
В этом случае важно учитывать CSS-ограничения:
pointer-events, transform-origin и
position, поскольку они влияют на корректность
drag-поведения.
При работе со слоями GeoJSON маркер часто выступает
визуальным представлением feature. После drag необходимо обновлять
источник через setData.
const geojson = {
type: 'FeatureCollection',
features: [{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [69.2401, 41.2995]
}
}]
};
map.on('load', () => {
map.addSource('point', {
type: 'geojson',
data: geojson
});
map.addLayer({
id: 'point-layer',
type: 'circle',
source: 'point',
paint: {
'circle-radius': 6,
'circle-color': '#ff0000'
}
});
marker.on('dragend', () => {
const pos = marker.getLngLat();
geojson.features[0].geometry.coordinates = [pos.lng, pos.lat];
map.getSource('point').setData(geojson);
});
});
Такой подход обеспечивает синхронное обновление слоя без пересоздания карты.
Часто требуется ограничить область перемещения. Это реализуется через
проверку координат в обработчике drag.
const bounds = [
[69.0, 41.0],
[69.5, 41.5]
];
marker.on('drag', () => {
const pos = marker.getLngLat();
const lng = Math.max(bounds[0][0], Math.min(bounds[1][0], pos.lng));
const lat = Math.max(bounds[0][1], Math.min(bounds[1][1], pos.lat));
marker.setLngLat([lng, lat]);
});
Подобная логика используется для ограничения редактирования внутри административных зон или пользовательских областей.
Событие drag генерируется на каждом кадре. При привязке
к тяжелым операциям (рендер UI, запросы) используется
requestAnimationFrame.
let frame;
marker.on('drag', () => {
if (frame) return;
frame = requestAnimationFrame(() => {
const pos = marker.getLngLat();
updateUI(pos);
frame = null;
});
});
Это снижает нагрузку и предотвращает блокировку интерфейса при интенсивном перемещении.
В архитектурах с глобальным состоянием (Redux, Zustand или аналогами) маркер часто диспатчит события вместо прямого обновления данных.
marker.on('dragend', () => {
const { lng, lat } = marker.getLngLat();
dispatch({
type: 'UPDATE_MARKER_POSITION',
payload: { lng, lat }
});
});
Такая схема обеспечивает детерминированное обновление состояния карты и упрощает отладку координатных изменений.