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

В рамках Google Maps JavaScript API информационное окно (google.maps.InfoWindow) представляет собой всплывающий контейнер, привязанный к карте или маркеру, предназначенный для отображения произвольного HTML-содержимого. При работе с несколькими объектами карты возникает необходимость управления множеством таких окон одновременно, что требует аккуратной организации состояния и жизненного цикла каждого экземпляра.

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


Создание нескольких информационных окон

Каждое информационное окно создаётся как независимый объект:

const infoWindowA = new google.maps.InfoWindow({
  content: '<div>Маркер A</div>'
});

const infoWindowB = new google.maps.InfoWindow({
  content: '<div>Маркер B</div>'
});

Каждый экземпляр хранит собственное состояние, DOM-содержимое и позиционирование. Привязка осуществляется через метод open, который связывает окно с картой или конкретным маркером.

infoWindowA.open({
  anchor: markerA,
  map: map
});

infoWindowB.open({
  anchor: markerB,
  map: map
});

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


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

При сценарии, где каждый маркер имеет собственное постоянное информационное окно, используется модель «один маркер — одно окно».

markers.forEach(({ position, text }) => {
  const marker = new google.maps.Marker({
    position,
    map
  });

  const infoWindow = new google.maps.InfoWindow({
    content: `<div>${text}</div>`
  });

  marker.addListener('click', () => {
    infoWindow.open({
      anchor: marker,
      map
    });
  });
});

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


Централизованное управление окнами

Распространённой архитектурой является использование одного активного информационного окна для всей карты. В этом случае новое открытие автоматически закрывает предыдущее состояние.

const sharedInfoWindow = new google.maps.InfoWindow();

markers.forEach((marker, index) => {
  marker.addListener('click', () => {
    sharedInfoWindow.setContent(`Маркер ${index}`);
    sharedInfoWindow.open({
      anchor: marker,
      map
    });
  });
});

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


Явное закрытие предыдущих окон

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

const infoWindows = [];

markers.forEach((marker, i) => {
  const infoWindow = new google.maps.InfoWindow({
    content: `Маркер ${i}`
  });

  infoWindows.push(infoWindow);

  marker.addListener('click', () => {
    infoWindows.forEach(w => w.close());

    infoWindow.open({
      anchor: marker,
      map
    });
  });
});

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


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

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

  • массив маркеров
  • массив окон или единый контроллер
  • индексная привязка
const state = markers.map((marker, i) => ({
  marker,
  infoWindow: new google.maps.InfoWindow({
    content: `Объект ${i}`
  })
}));

state.forEach(({ marker, infoWindow }) => {
  marker.addListener('click', () => {
    state.forEach(s => s.infoWindow.close());

    infoWindow.open({
      anchor: marker,
      map
    });
  });
});

Подобная модель удобна при синхронизации с внешними данными (например, API-ответами или состоянием приложения).


Проблема наложения и z-index

При одновременном открытии нескольких окон возникает проблема перекрытия. Внутренний механизм рендеринга использует z-index, который увеличивается при открытии окна, однако порядок не всегда соответствует логике пользовательского взаимодействия.

Для контроля используется:

  • последовательное закрытие окон
  • управление фокусом через close()
  • ограничение количества активных окон

Динамическое обновление содержимого

Множественные окна часто требуют изменения контента без пересоздания экземпляра.

infoWindow.setContent(`
  <div>
    <h4>Обновлённые данные</h4>
    <p>Время: ${Date.now()}</p>
  </div>
`);

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


Привязка окон к событиям карты

Информационные окна могут быть связаны не только с маркерами, но и с произвольными координатами карты:

map.addListener('click', (event) => {
  sharedInfoWindow.setContent(
    `Координаты: ${event.latLng.lat()}, ${event.latLng.lng()}`
  );

  sharedInfoWindow.setPosition(event.latLng);
  sharedInfoWindow.open(map);
});

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


Интеграция с динамическими списками объектов

При работе с данными, поступающими из внешних источников, структура часто формируется на лету:

function renderLocations(locations) {
  const windows = [];

  locations.forEach(loc => {
    const marker = new google.maps.Marker({
      position: loc.position,
      map
    });

    const infoWindow = new google.maps.InfoWindow({
      content: `<strong>${loc.name}</strong>`
    });

    windows.push({ marker, infoWindow });

    marker.addListener('click', () => {
      windows.forEach(w => w.infoWindow.close());

      infoWindow.open({
        anchor: marker,
        map
      });
    });
  });
}

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


Производственные ограничения при большом количестве окон

При масштабировании до сотен объектов возникают характерные ограничения:

  • рост потребления памяти при множестве InfoWindow
  • увеличение количества DOM-узлов
  • задержки при открытии и закрытии окон
  • сложность синхронизации состояния

Оптимизация обычно достигается за счёт:

  • перехода к одному переиспользуемому окну
  • виртуализации данных
  • кластеризации маркеров (внешне через MarkerClusterer)
  • ленивого создания окон

Контроль жизненного цикла окон

Информационные окна не требуют явного удаления, однако их закрытие освобождает ресурсы визуального слоя. При динамическом удалении маркеров целесообразно также разрывать связи событий:

marker.setMap(null);
infoWindow.close();

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


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

Типовые модели управления множественными окнами включают:

  • одиночный активный контекст (single active InfoWindow)
  • независимые окна (multi-window overlay)
  • переключаемые окна (stateful reuse)
  • ленивые окна (создание по клику)

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


Синхронизация с пользовательскими событиями

Информационные окна часто синхронизируются с состоянием интерфейса:

  • hover вместо click
  • открытие при выборе из списка
  • синхронизация с фильтрами данных
  • автоматическое закрытие при изменении слоя карты
marker.addListener('mouseover', () => {
  sharedInfoWindow.setContent('Hover-состояние');
  sharedInfoWindow.open({ anchor: marker, map });
});

marker.addListener('mouseout', () => {
  sharedInfoWindow.close();
});

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