В HERE Maps JavaScript API всплывающие окна (InfoBubble и InfoWindow в UI-слое) используются не только для отображения статической информации, но и как полноценные контейнеры для интерактивных элементов интерфейса. Такой подход позволяет превращать карту в прикладной инструмент: маршрутизатор, каталог объектов, панель управления слоями и даже мини-приложение.
Ключевой особенностью реализации является то, что содержимое окна формируется как HTML-структура, которая затем вставляется в DOM, а значит — может включать кнопки, формы, переключатели и любые другие интерактивные элементы.
Всплывающие окна в HERE Maps поддерживают передачу HTML-строки, которая рендерится внутри контейнера окна.
Основной принцип:
Пример базовой структуры:
const bubble = new H.ui.InfoBubble(position, {
content: `
<div class="popup">
<h3>Объект на карте</h3>
<p>Описание точки интереса</p>
<button id="action-btn">Подробнее</button>
</div>
`
});
ui.addBubble(bubble);
В таком виде кнопка уже существует в DOM, но обработчик событий ещё не подключён, что требует дополнительной логики.
Поскольку содержимое создаётся динамически, прямое навешивание обработчиков до вставки в DOM невозможно. Основные подходы:
Наиболее устойчивый метод — использование делегирования:
document.addEventListener('click', (event) => {
if (event.target && event.target.id === 'action-btn') {
console.log('Кнопка нажата');
}
});
Этот подход работает даже при пересоздании окна, так как обработчик привязан к документу.
Иногда требуется доступ к конкретному экземпляру окна. Тогда обработчики навешиваются после вставки DOM:
bubble.addEventListener('statechange', () => {
const btn = document.getElementById('action-btn');
if (btn) {
btn.oncl ick = () => {
alert('Действие выполнено');
};
}
});
Такой метод требует контроля жизненного цикла окна, так как элементы могут пересоздаваться.
Всплывающие окна в HERE Maps не являются постоянными DOM-элементами. Они:
Это означает, что любые интерактивные элементы должны быть привязаны с учётом динамичности DOM.
Основные состояния:
open — окно отображаетсяclose — окно уничтожает DOM-содержимоеupdate — контент пересоздаётсяПри проектировании логики важно учитывать, что ссылки на элементы DOM могут становиться недействительными.
Всплывающие окна могут содержать полноценные формы, включая input, select и textarea.
Пример:
const bubble = new H.ui.InfoBubble(position, {
content: `
<div>
<label>Имя:</label>
<input type="text" id="username" />
<button id="send-btn">Отправить</button>
</div>
`
});
Обработка данных:
document.addEventListener('click', (e) => {
if (e.target.id === 'send-btn') {
const input = document.getElementById('username');
console.log(input.value);
}
});
Особенности:
Интерактивные элементы внутри окна могут конфликтовать с жестами карты (drag, zoom). Чтобы избежать этого, необходимо блокировать всплытие событий.
document.addEventListener('mousedown', (e) => {
if (e.target.closest('.popup')) {
e.stopPropagation();
}
});
Дополнительно:
document.addEventListener('wheel', (e) => {
if (e.target.closest('.popup')) {
e.stopPropagation();
}
}, { passive: true });
Это предотвращает нежелательное масштабирование карты при работе с элементами UI.
Интерактивные окна часто требуют изменения содержимого без закрытия.
Подходы:
bubble.setContent(`
<div>
<p>Загрузка данных...</p>
</div>
`);
const container = document.querySelector('.popup');
container.innerHTML = '<p>Данные обновлены</p>';
Первый способ предпочтителен, так как сохраняет согласованность с UI-слоем карты.
Внутри всплывающих окон можно размещать:
Пример панели действий:
content: `
<div>
<h4>Маршрут</h4>
<button id="route-btn">Построить</button>
<button id="save-btn">Сохранить</button>
</div>
`
Обработчики:
document.addEventListener('click', (e) => {
if (e.target.id === 'route-btn') {
console.log('Строим маршрут');
}
if (e.target.id === 'save-btn') {
console.log('Сохраняем объект');
}
});
При отображении нескольких всплывающих окон одновременно возникает проблема идентификаторов элементов DOM.
Рекомендуемые практики:
Пример:
const id = `btn-${Math.random().toString(36).substr(2, 9)}`;
content: `
<button id="${id}">Нажать</button>
`
Обработчик:
document.addEventListener('click', (e) => {
if (e.target.id.startsWith('btn-')) {
console.log('Кнопка конкретного окна');
}
});
Более устойчивый подход — data-атрибуты:
content: `
<button data-action="route">Маршрут</button>
<button data-action="info">Информация</button>
`
Обработка:
document.addEventListener('click', (e) => {
const action = e.target.dataset.action;
if (action === 'route') {
console.log('route action');
}
if (action === 'info') {
console.log('info action');
}
});
Этот метод масштабируется лучше при росте интерфейса.
Так как содержимое окна представляет собой HTML, необходимо учитывать потенциальные риски:
Рекомендуется:
Пример небезопасного подхода:
content: `<div>${userInput}</div>`
Безопасный вариант:
const safe = userInput.replace(/</g, "<").replace(/>/g, ">");
При частом открытии окон с интерактивными элементами важно:
Особенно критично:
Важная архитектурная особенность — привязка окна к геообъекту:
const marker = new H.map.Marker(position);
marker.addEventListener('tap', () => {
ui.addBubble(new H.ui.InfoBubble(position, {
content: '<div>Объект</div>'
}));
});
Это позволяет создавать контекстные интерфейсы, зависящие от выбранного объекта.
Интерактивные окна могут реагировать на события карты:
Пример:
map.addEventListener('mapviewchange', () => {
console.log('Карта изменилась');
});
На основе этих событий можно динамически менять содержимое окна, например, показывать разный уровень детализации информации.
Интерактивные элементы часто связаны с внешними API:
Пример вызова:
document.addEventListener('click', async (e) => {
if (e.target.id === 'route-btn') {
const response = await fetch('/api/route');
const data = await response.json();
console.log(data);
}
});
Внутри окон можно использовать собственные UI-библиотеки:
Общий принцип остаётся одинаковым: контейнер окна — это DOM-узел, в который можно монтировать интерфейс.
ReactDOM.render(<PopupComponent />, document.getElementById('popup-root'));
Иногда требуется закрывать окно при действии пользователя:
document.addEventListener('click', (e) => {
if (e.target.id === 'close-btn') {
bubble.close();
}
});
Это позволяет создавать контролируемые сценарии взаимодействия, где окно становится временной панелью управления.