В Mapbox GL JS маркеры представляют собой самостоятельные объекты слоя интерфейса, которые живут поверх WebGL-карты и могут быть связаны с произвольными данными, DOM-элементами и событиями. Привязка к маркерам в данном контексте означает установку устойчивого соответствия между географической сущностью (координатами), визуальным представлением (DOM или canvas-элементом) и логикой взаимодействия (события, всплывающие окна, состояние приложения).
Маркер в Mapbox GL JS создаётся как объект
mapboxgl.Marker, который может принимать DOM-элемент и
координаты. Привязка начинается с фиксации географической точки:
const marker = new mapboxgl.Marker()
.setLngLat([69.2401, 41.2995])
.addTo(map);
Здесь формируется минимальная связь:
Эта связь статична до тех пор, пока координаты не изменяются через
setLngLat.
В реальных приложениях маркеры почти никогда не существуют сами по себе. Каждый маркер связывается с объектом данных, например точкой интереса:
const place = {
id: 1,
name: "Office",
coordinates: [69.2401, 41.2995],
type: "business"
};
const marker = new mapboxgl.Marker()
.setLngLat(place.coordinates)
.addTo(map);
marker.__data = place;
Привязка через пользовательское поле (__data) создаёт
простой механизм обратной идентификации: визуальный элемент → исходная
модель данных.
Более структурированный подход использует Map:
const markerMap = new Map();
const marker = new mapboxgl.Marker()
.setLngLat(place.coordinates)
.addTo(map);
markerMap.set(marker, place);
Такое разделение устраняет засорение объекта маркера и упрощает управление жизненным циклом.
Основной механизм интерактивной привязки — события DOM-элемента маркера. Каждый маркер содержит root-элемент, на который можно повесить обработчики:
const el = document.createElement('div');
el.className = 'custom-marker';
el.addEventListener('click', () => {
console.log('Маркер активирован');
});
new mapboxgl.Marker(el)
.setLngLat([69.2401, 41.2995])
.addTo(map);
Таким образом формируется связь:
При наличии данных привязка становится контекстной:
el.addEventListener('click', () => {
showSidebar(place);
});
Одним из наиболее распространённых сценариев является привязка
Popup к маркеру. Popup “прикрепляется” к маркеру и следует
за ним:
const popup = new mapboxgl.Popup({ offset: 25 })
.setText(place.name);
new mapboxgl.Marker()
.setLngLat(place.coordinates)
.setPopup(popup)
.addTo(map);
В этом случае маркер становится якорем (anchor), а popup — зависимой сущностью.
Более сложная привязка включает динамическое содержимое:
const popup = new mapboxgl.Popup({ offset: 25 });
const marker = new mapboxgl.Marker()
.setLngLat(place.coordinates)
.addTo(map);
marker.getElement().addEventListener('click', () => {
popup
.setHTML(`<strong>${place.name}</strong>`)
.setLngLat(place.coordinates)
.addTo(map);
});
Здесь маркер не только визуальный объект, но и триггер обновления popup.
Маркер может полностью заменять стандартную иконку кастомным HTML-узлом. Это открывает возможность связывать сложные UI-компоненты с географическими точками:
const el = document.createElement('div');
el.innerHTML = `
<div class="pin">
<span class="pin-title">${place.name}</span>
</div>
`;
new mapboxgl.Marker(el)
.setLngLat(place.coordinates)
.addTo(map);
В этом случае DOM становится частью модели представления, а привязка
осуществляется через замыкание и состояние объекта
place.
При интерактивных картах часто требуется обратная привязка: изменение состояния маркера должно отражаться в данных приложения.
marker.on('dragend', () => {
const lngLat = marker.getLngLat();
place.coordinates = [lngLat.lng, lngLat.lat];
});
Здесь создаётся двунаправленная связь:
Такая синхронизация критична для редакторов геоданных.
Частый сценарий — связка маркера с элементом списка. Каждый элемент UI связывается с маркером через идентификатор:
const marker = new mapboxgl.Marker()
.setLngLat(place.coordinates)
.addTo(map);
const item = document.querySelector(`#item-${place.id}`);
item.addEventListener('mouseenter', () => {
marker.getElement().classList.add('active');
});
item.addEventListener('mouseleave', () => {
marker.getElement().classList.remove('active');
});
Так формируется логическая пара:
При большом количестве объектов необходима централизованная привязка через коллекции:
const markers = [];
places.forEach(place => {
const marker = new mapboxgl.Marker()
.setLngLat(place.coordinates)
.addTo(map);
markers.push({ marker, place });
});
Расширенная структура:
const markerIndex = new Map();
places.forEach(place => {
const marker = new mapboxgl.Marker()
.setLngLat(place.coordinates)
.addTo(map);
markerIndex.set(place.id, marker);
});
Это позволяет выполнять быстрый доступ:
При изменении координат в модели требуется синхронное обновление маркера:
function updatePlaceCoordinates(id, coords) {
const marker = markerIndex.get(id);
if (marker) {
marker.setLngLat(coords);
}
}
Эта операция является реактивной привязкой от данных к визуальному слою.
Маркер часто отражает состояние (active, selected, filtered):
function setActiveMarker(id) {
markerIndex.forEach((marker, key) => {
const el = marker.getElement();
if (key === id) {
el.classList.add('active');
} else {
el.classList.remove('active');
}
});
}
Таким образом создаётся глобальная модель состояния маркеров, синхронизированная с UI.
Жизненный цикл маркера должен быть связан с жизненным циклом данных:
function removePlace(id) {
const marker = markerIndex.get(id);
if (marker) {
marker.remove();
markerIndex.delete(id);
}
}
Удаление разрывает все связи:
При фильтрации данных привязка становится условной:
function applyFilter(predicate) {
markerIndex.forEach((marker, id) => {
const place = places.find(p => p.id === id);
const visible = predicate(place);
marker.getElement().style.display = visible ? 'block' : 'none';
});
}
Здесь маркер связан не только с данными, но и с логикой отображения.
В более масштабных системах привязка маркеров становится частью архитектуры управления состоянием, где каждый маркер представляет синхронизированную визуализацию сущности, а все изменения проходят через единый поток обновлений между моделью, представлением и событиями карты.