Привязка к маркерам

В 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);

Здесь формируется минимальная связь:

  • координаты → географическая позиция
  • объект Marker → визуальная сущность на карте
  • карта → контейнер рендеринга

Эта связь статична до тех пор, пока координаты не изменяются через 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);

Таким образом формируется связь:

  • клик → DOM-событие
  • DOM-событие → бизнес-логика
  • бизнес-логика → изменение состояния приложения

При наличии данных привязка становится контекстной:

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.

Привязка пользовательского DOM к маркеру

Маркер может полностью заменять стандартную иконку кастомным 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];
});

Здесь создаётся двунаправленная связь:

  • модель → маркер (initial render)
  • маркер → модель (drag event)

Такая синхронизация критична для редакторов геоданных.

Привязка маркеров к спискам интерфейса

Частый сценарий — связка маркера с элементом списка. Каждый элемент 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);
});

Это позволяет выполнять быстрый доступ:

  • получение маркера по id
  • обновление позиции
  • удаление и пересоздание

Обновление привязки при изменении данных

При изменении координат в модели требуется синхронное обновление маркера:

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);
  }
}

Удаление разрывает все связи:

  • карта → маркер
  • маркер → DOM
  • маркер → события

Сложные сценарии привязки: фильтрация и кластеризация

При фильтрации данных привязка становится условной:

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';
  });
}

Здесь маркер связан не только с данными, но и с логикой отображения.


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