В OpenLayers взаимодействие «наведение курсора» реализуется не как
отдельный встроенный hover-ивент для объектов, а через низкоуровневое
событие карты pointermove. Оно срабатывает при каждом
перемещении указателя мыши или пера и служит базовой точкой для
определения объектов под курсором.
map.on('pointermove', function (event) {
// event.coordinate — координаты в системе карты
// event.pixel — пиксель на экране
});
Основная особенность подхода заключается в том, что hover не является сущностью слоя или объекта. Это вычисляемое состояние, которое определяется в момент движения указателя.
Ключевой механизм — проверка попадания пикселя в геометрию объектов
слоя. Для этого используется forEachFeatureAtPixel.
map.on('pointermove', function (event) {
map.forEachFeatureAtPixel(event.pixel, function (feature, layer) {
console.log(feature.get('name'));
});
});
Функция перебирает все объекты, находящиеся под пикселем курсора. При наличии нескольких слоёв возвращается первый найденный объект в порядке отрисовки.
Дополнительные параметры позволяют управлять чувствительностью:
map.forEachFeatureAtPixel(event.pixel, function (feature) {
// логика hover
}, {
hitTolerance: 5
});
hitTolerance расширяет область попадания, что критично
для тонких линий и мелких точек.
Наиболее распространённый hover-эффект — временное изменение стиля объекта при наведении. Это реализуется через сохранение состояния текущего выделенного объекта.
let hoveredFeature = null;
map.on('pointermove', function (event) {
let found = null;
map.forEachFeatureAtPixel(event.pixel, function (feature) {
found = feature;
});
if (hoveredFeature !== found) {
if (hoveredFeature) {
hoveredFeature.setStyle(undefined);
}
if (found) {
found.setStyle(new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'red',
width: 3
}),
fill: new ol.style.Fill({
color: 'rgba(255,0,0,0.2)'
})
}));
}
hoveredFeature = found;
}
});
Такой подход применим для небольших наборов данных. При больших объёмах он приводит к перегрузке из-за постоянного создания стилей.
Более стабильный способ — использование свойства объекта и динамического стиля слоя.
const vectorLayer = new ol.layer.Vector({
source: vectorSource,
style: function (feature) {
const hovered = feature.get('hovered');
return new ol.style.Style({
stroke: new ol.style.Stroke({
color: hovered ? '#ff0000' : '#3399CC',
width: hovered ? 3 : 1
}),
fill: new ol.style.Fill({
color: hovered ? 'rgba(255,0,0,0.3)' : 'rgba(51,153,204,0.1)'
})
});
}
});
Обновление состояния:
map.on('pointermove', function (event) {
vectorSource.forEachFeature(function (f) {
if (f.get('hovered')) {
f.set('hovered', false);
}
});
map.forEachFeatureAtPixel(event.pixel, function (feature) {
feature.set('hovered', true);
});
});
Такой подход лучше масштабируется, так как стиль централизован.
Изменение курсора — важный визуальный сигнал взаимодействия.
map.on('pointermove', function (event) {
const hit = map.hasFeatureAtPixel(event.pixel);
map.getTargetElement().style.cursor = hit ? 'pointer' : '';
});
Для более точного контроля используется
forEachFeatureAtPixel, позволяющий учитывать только
определённые слои.
Постоянное выполнение логики на pointermove создаёт
нагрузку. Основные оптимизации:
let lastTime = 0;
map.on('pointermove', function (event) {
const now = Date.now();
if (now - lastTime < 16) return;
lastTime = now;
// обработка
});
map.on('pointermove', function (event) {
let hit = false;
map.forEachFeatureAtPixel(event.pixel, function () {
hit = true;
return true;
});
if (!hit) return;
});
map.forEachFeatureAtPixel(event.pixel, function (feature, layer) {
if (layer !== targetLayer) return;
});
Hover часто сопровождается всплывающей подсказкой через
Overlay.
const tooltip = new ol.Overlay({
element: document.getElementById('tooltip'),
offset: [10, 0]
});
map.addOverlay(tooltip);
Логика отображения:
map.on('pointermove', function (event) {
const feature = map.forEachFeatureAtPixel(event.pixel, f => f);
if (feature) {
const coord = event.coordinate;
tooltip.setPosition(coord);
tooltip.getElement().innerHTML = feature.get('name');
tooltip.getElement().style.display = 'block';
} else {
tooltip.getElement().style.display = 'none';
}
});
Встроенный механизм взаимодействия — Select. Он
поддерживает активацию по наведению через условие.
const selectHover = new ol.interaction.Select({
condition: ol.events.condition.pointerMove
});
map.addInteraction(selectHover);
Дальнейшая работа идёт через события selection:
selectHover.on('select', function (event) {
console.log(event.selected);
});
Этот подход снижает необходимость ручного управления стилями.
При наличии нескольких vector-слоёв порядок важен. Hover определяется первым найденным объектом.
Фильтрация слоя:
map.forEachFeatureAtPixel(event.pixel, function (feature, layer) {
if (layer.get('hoverEnabled')) {
return feature;
}
});
Приоритет слоёв может регулироваться через zIndex, но
логика hover остаётся на уровне кода.
При использовании кластеров (ol.source.Cluster) hover
применяется к группам объектов.
map.forEachFeatureAtPixel(event.pixel, function (feature) {
const features = feature.get('features');
if (features && features.length > 1) {
// кластер
}
});
Поведение обычно разделяется:
Особенности геометрии приводят к неточностям попадания курсора. Основные решения:
hitToleranceCircle для точек вместо пиксельной
проверкиmap.forEachFeatureAtPixel(event.pixel, function (feature) {
const geometry = feature.getGeometry();
const extent = geometry.getExtent();
});
Критический момент — корректное очищение состояния при уходе курсора с объекта.
map.on('pointermove', function (event) {
if (event.dragging) return;
const feature = map.forEachFeatureAtPixel(event.pixel, f => f);
if (!feature && hoveredFeature) {
hoveredFeature.set('hovered', false);
hoveredFeature = null;
}
});
Это предотвращает «залипание» подсветки при быстром перемещении курсора или зуме.