Множественные информационные окна

Множественные информационные окна в картографических приложениях на базе HERE Maps API требуют отдельной архитектуры управления, поскольку стандартная модель UI предполагает ограниченное количество одновременно отображаемых всплывающих элементов. При увеличении числа маркеров и интерактивных объектов возникает задача координации состояния окон, предотвращения их наложения и контроля жизненного цикла каждого экземпляра.

В HERE Maps JavaScript API (версии 3.x) для отображения всплывающих окон используется компонент H.ui.InfoBubble. Он привязывается к географической точке и отображается поверх карты в UI-слое.

Создание базового информационного окна:

var bubble = new H.ui.InfoBubble(
  { lat: 52.5200, lng: 13.4050 },
  { content: '<b>Берлин</b><br/>Столица Германии' }
);

ui.addBubble(bubble);

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


Проблема множественного отображения

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

  • окна перекрывают друг друга;
  • часть окон выходит за пределы viewport;
  • пользователь теряет контекст, какое окно относится к какому маркеру;
  • отсутствует единая логика закрытия.

Без централизованного управления интерфейс быстро становится перегруженным.


Центральный менеджер информационных окон

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

function InfoBubbleManager(ui) {
  this.ui = ui;
  this.activeBubble = null;
}

InfoBubbleManager.prototype.open = function(position, content) {
  if (this.activeBubble) {
    this.ui.removeBubble(this.activeBubble);
  }

  this.activeBubble = new H.ui.InfoBubble(position, {
    content: content
  });

  this.ui.addBubble(this.activeBubble);
};

InfoBubbleManager.prototype.close = function() {
  if (this.activeBubble) {
    this.ui.removeBubble(this.activeBubble);
    this.activeBubble = null;
  }
};

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


Поддержка нескольких окон одновременно

В ряде сценариев требуется отображение нескольких окон одновременно, например:

  • сравнение объектов на карте;
  • отображение кластеров событий;
  • аналитические панели для групп точек.

В этом случае используется коллекция окон вместо единственной ссылки.

function MultiBubbleManager(ui) {
  this.ui = ui;
  this.bubbles = [];
}

MultiBubbleManager.prototype.open = function(position, content) {
  var bubble = new H.ui.InfoBubble(position, {
    content: content
  });

  this.ui.addBubble(bubble);
  this.bubbles.push(bubble);

  return bubble;
};

MultiBubbleManager.prototype.closeAll = function() {
  this.bubbles.forEach(function(b) {
    this.ui.removeBubble(b);
  });

  this.bubbles = [];
};

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

Частый сценарий — связывание InfoBubble с объектами H.map.Marker. Важно учитывать, что маркер сам по себе не хранит состояние окна, поэтому связь реализуется через события.

var marker = new H.map.Marker({ lat: 52.5, lng: 13.4 });
map.addObject(marker);

marker.addEventListener('tap', function(evt) {
  var coord = map.screenToGeo(
    evt.currentPointer.viewportX,
    evt.currentPointer.viewportY
  );

  infoManager.open(coord, '<b>Точка интереса</b>');
});

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


Предотвращение наложения окон

При множественных окнах возникает проблема перекрытия. Одно из решений — смещение окон относительно маркера.

var bubble = new H.ui.InfoBubble(position, {
  content: content,
  offset: { x: 0, y: -20 }
});

Другой подход — динамическая коррекция позиции на основе масштаба карты:

  • при большом zoom окна размещаются ближе к маркеру;
  • при малом zoom увеличивается вертикальное смещение.

Использование событий жизненного цикла

Для контроля состояния окон применяются события карты и UI:

  • mapviewchange — изменение области отображения;
  • tap — закрытие окон при клике вне маркера;
  • пользовательские события UI.

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

map.addEventListener('tap', function(evt) {
  if (evt.target instanceof H.map.Marker === false) {
    infoManager.close();
  }
});

Это предотвращает накопление неактуальных окон.


Связывание окон с динамическими данными

При работе с API данных (например, подгрузка объектов с сервера) окна часто создаются асинхронно.

fetch('/api/points')
  .then(res => res.json())
  .then(data => {
    data.forEach(function(item) {
      var marker = new H.map.Marker(item.coords);
      map.addObject(marker);

      marker.addEventListener('tap', function() {
        infoManager.open(item.coords, item.html);
      });
    });
  });

В этом случае важно учитывать:

  • задержки загрузки;
  • повторное создание маркеров;
  • синхронизацию состояния окон с данными.

Оптимизация при большом количестве объектов

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

1. Ленивая инициализация окон Окно создаётся только при взаимодействии пользователя.

2. Переиспользование одного экземпляра Один InfoBubble перемещается между точками вместо создания новых объектов.

3. Кластеризация маркеров Группировка объектов снижает количество потенциальных окон.

var clusteringProvider = new H.clustering.Provider(dataPoints);
var layer = new H.map.layer.ObjectLayer(clusteringProvider);
map.addLayer(layer);

Управление z-index и слоями

При одновременном отображении нескольких окон критически важно управление порядком отрисовки. HERE UI позволяет влиять на приоритет отображения через порядок добавления объектов:

  • последние добавленные окна отображаются поверх предыдущих;
  • удаление и повторное добавление изменяет визуальный слой.

Для сложных интерфейсов применяется стратегия пересоздания активного окна при изменении фокуса.


Интеграция с кастомными HTML-шаблонами

Содержимое окон может включать произвольный HTML, что позволяет строить сложные интерфейсы:

var content =
  '<div class="bubble">' +
  '<h3>Объект</h3>' +
  '<button oncl ick="handleAction()">Действие</button>' +
  '</div>';

infoManager.open(position, content);

При этом необходимо учитывать:

  • изоляцию событий;
  • предотвращение утечек памяти;
  • очистку обработчиков при закрытии окна.

Синхронизация с состоянием карты

Информационные окна должны реагировать на изменение карты:

  • перемещение карты — корректировка позиции;
  • масштабирование — адаптация размеров контента;
  • смена проекции — пересчёт координат.

Пример пересоздания окна при изменении масштаба:

map.addEventListener('mapviewchange', function() {
  if (infoManager.activeBubble) {
    var pos = infoManager.activeBubble.getPosition();
    var content = infoManager.activeBubble.getContent();

    infoManager.open(pos, content);
  }
});

Такой подход обеспечивает корректное позиционирование при любых трансформациях карты.


Архитектура масштабируемой системы окон

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

  • слой маркеров (данные);
  • слой взаимодействия (события);
  • слой UI (InfoBubble);
  • менеджер состояния окон.

Разделение ответственности позволяет:

  • контролировать жизненный цикл окон;
  • избегать дублирования UI-логики;
  • масштабировать систему до тысяч объектов без деградации интерфейса.