В OpenLayers отображение всплывающих элементов поверх карты строится
на механизме ol/Overlay. Overlay привязывает HTML-элемент к
координатам карты и синхронизирует его положение при перемещении и
масштабировании.
Ключевая особенность:
Базовая структура overlay:
import Overlay from 'ol/Overlay.js';
const tooltipElement = document.createElement('div');
tooltipElement.className = 'ol-tooltip';
const tooltipOverlay = new Overlay({
element: tooltipElement,
offset: [10, 0],
positioning: 'bottom-left',
stopEvent: false
});
map.addOverlay(tooltipOverlay);
Параметр stopEvent: false позволяет событиям карты
проходить сквозь тултип, что важно для интерактивных сценариев.
Тултипы часто используются для отображения атрибутов объектов при
движении мыши над картой. Основной механизм — обработка события
pointermove.
map.on('pointermove', function (event) {
const feature = map.forEachFeatureAtPixel(event.pixel, function (feature) {
return feature;
});
if (feature) {
const coordinate = event.coordinate;
const name = feature.get('name');
tooltipElement.innerHTML = name;
tooltipOverlay.setPosition(coordinate);
} else {
tooltipOverlay.setPosition(undefined);
}
});
Метод forEachFeatureAtPixel выполняет hit-detection по
слоям и возвращает первый найденный объект.
Особенности поведения:
setPosition(undefined)Внешний вид полностью определяется стилями:
.ol-tooltip {
background: rgba(20, 20, 20, 0.85);
color: #fff;
padding: 6px 10px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
}
Часто применяется визуальная привязка стрелкой:
.ol-tooltip::after {
content: '';
position: absolute;
top: 100%;
left: 10px;
border-width: 6px;
border-style: solid;
border-color: rgba(20, 20, 20, 0.85) transparent transparent transparent;
}
Тултипы при наведении обычно кратковременные, тогда как
информационные окна фиксируются по клику. Для этого используется тот же
Overlay, но с обработкой события click.
map.on('click', function (event) {
const feature = map.forEachFeatureAtPixel(event.pixel, f => f);
if (feature) {
const coord = event.coordinate;
popupElement.innerHTML = `
<div class="popup-title">${feature.get('name')}</div>
<div class="popup-body">${feature.get('description')}</div>
`;
popupOverlay.setPosition(coord);
} else {
popupOverlay.setPosition(undefined);
}
});
Поведение отличается:
Overlay поддерживает автоматическое смещение при приближении к краям карты:
const popupOverlay = new Overlay({
element: popupElement,
autoPan: {
animation: {
duration: 250
}
}
});
Механизм autoPan сдвигает центр карты так, чтобы popup
оставался видимым.
Дополнительные параметры:
autoPanMargin — отступ от краяautoPanAnimation — управление плавностьюПри наличии нескольких vector layers важно управлять порядком выбора объектов.
map.on('pointermove', function (event) {
let featureFound = null;
map.forEachFeatureAtPixel(event.pixel, function (feature, layer) {
if (layer && layer.get('selectable')) {
featureFound = feature;
return true;
}
});
if (featureFound) {
tooltipElement.innerHTML = featureFound.get('title');
tooltipOverlay.setPosition(event.coordinate);
}
});
Фильтрация по слою позволяет исключать служебные или фоновые данные.
При использовании кластеризации (ol/source/Cluster)
объект содержит массив оригинальных features.
map.on('pointermove', function (event) {
const feature = map.forEachFeatureAtPixel(event.pixel, f => f);
if (feature) {
const features = feature.get('features');
if (features && features.length > 1) {
tooltipElement.innerHTML = `Объектов: ${features.length}`;
} else {
tooltipElement.innerHTML = features[0].get('name');
}
tooltipOverlay.setPosition(event.coordinate);
}
});
Особенности:
get('features')Информационные панели часто реализуются как отдельные DOM-блоки, синхронизированные с выбором объектов карты.
Базовая модель взаимодействия:
let selectedFeature = null;
map.on('click', function (event) {
selectedFeature = map.forEachFeatureAtPixel(event.pixel, f => f);
renderSidebar(selectedFeature);
});
Функция рендера:
function renderSidebar(feature) {
const panel = document.getElementById('sidebar');
if (!feature) {
panel.innerHTML = '';
return;
}
panel.innerHTML = `
<h3>${feature.get('name')}</h3>
<p>${feature.get('description')}</p>
`;
}
При динамических данных (WebSocket, API обновления) требуется реактивное обновление панели:
vectorSource.on('change', function () {
if (selectedFeature) {
renderSidebar(selectedFeature);
}
});
Особенности:
Типовая архитектура включает два уровня представления:
Пример приоритета:
map.on('pointermove', handleTooltip);
map.on('click', handlePopupAndSidebar);
Такое разделение снижает нагрузку на DOM и упрощает управление состоянием.
Для серверных слоёв применяется запрос
GetFeatureInfo.
map.on('click', function (event) {
const viewResolution = map.getView().getResolution();
const url = wmsLayer.getSource().getFeatureInfoUrl(
event.coordinate,
viewResolution,
'EPSG:3857',
{ INFO_FORMAT: 'application/json' }
);
fetch(url)
.then(res => res.json())
.then(data => {
popupElement.innerHTML = JSON.stringify(data, null, 2);
popupOverlay.setPosition(event.coordinate);
});
});
Особенности:
При изменении масштаба часто требуется скрывать временные тултипы:
map.getView().on('change:resolution', function () {
tooltipOverlay.setPosition(undefined);
});
Причина:
Событие pointermove генерируется часто, поэтому
требуется оптимизация:
requestAnimationFramelet lastFeature = null;
map.on('pointermove', function (event) {
const feature = map.forEachFeatureAtPixel(event.pixel, f => f);
if (feature === lastFeature) return;
lastFeature = feature;
if (feature) {
tooltipElement.innerHTML = feature.get('name');
tooltipOverlay.setPosition(event.coordinate);
} else {
tooltipOverlay.setPosition(undefined);
}
});
Сложные интерфейсы используют несколько overlay одновременно:
Каждый overlay управляется независимо:
map.addOverlay(tooltipOverlay);
map.addOverlay(popupOverlay);
map.addOverlay(highlightOverlay);
Согласованность достигается через единый источник состояния выбранного объекта и текущего hover-элемента.