Изменение курсора

В веб-картах на базе MapLibre GL JS курсор выступает ключевым индикатором состояния взаимодействия. Он отражает доступные действия: наведение на объект, возможность перетаскивания, кликабельные элементы, режимы выбора. Управление курсором осуществляется через DOM-слой canvas, поверх которого рендерится карта WebGL.

Основной точкой управления служит:

map.getCanvas().style.cursor

Этот параметр напрямую изменяет CSS-свойство cursor у canvas-элемента карты.


Базовое управление курсором через API карты

Изменение курсора выполняется в любой момент жизненного цикла карты. Чаще всего используется связка событий наведения и ухода курсора с интерактивных слоёв.

Установка курсора по умолчанию

map.getCanvas().style.cursor = 'default';

Режим перетаскивания

map.getCanvas().style.cursor = 'grab';

Состояние захвата (dragging)

map.getCanvas().style.cursor = 'grabbing';

Реакция на события мыши

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

Наведение на интерактивный слой

map.on('mouseenter', 'cities-layer', () => {
    map.getCanvas().style.cursor = 'pointer';
});

map.on('mouseleave', 'cities-layer', () => {
    map.getCanvas().style.cursor = '';
});

Использование пустой строки возвращает курсор к стилю по умолчанию.


Использование hit-testing для изменения курсора

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

map.queryRenderedFeatures(point, options)

Пример логики динамического изменения состояния:

map.on('mousemove', (e) => {
    const features = map.queryRenderedFeatures(e.point);

    if (features.length > 0) {
        map.getCanvas().style.cursor = 'pointer';
    } else {
        map.getCanvas().style.cursor = '';
    }
});

Такой подход применяется при сложных сценах с множеством перекрывающихся слоёв.


Контекстные состояния курсора

В интерфейсах карт часто требуется отражать несколько режимов взаимодействия.

Наведение на объекты

map.on('mouseenter', 'pois', () => {
    map.getCanvas().style.cursor = 'pointer';
});

Выделение области

map.on('mousedown', () => {
    map.getCanvas().style.cursor = 'crosshair';
});

map.on('mouseup', () => {
    map.getCanvas().style.cursor = '';
});

Режим редактирования

map.on('mousemove', (e) => {
    if (editModeActive) {
        map.getCanvas().style.cursor = 'cell';
    }
});

Сочетание с draggable-состояниями карты

MapLibre GL JS имеет встроенную возможность перетаскивания карты. Управление курсором часто синхронизируется с этими состояниями.

map.on('dragstart', () => {
    map.getCanvas().style.cursor = 'grabbing';
});

map.on('dragend', () => {
    map.getCanvas().style.cursor = 'grab';
});

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

map.on('load', () => {
    map.getCanvas().style.cursor = 'grab';
});

Работа с pointer events вместо mouse events

Современные браузеры поддерживают Pointer Events, которые объединяют мышь, сенсор и перо.

map.on('pointermove', (e) => {
    const features = map.queryRenderedFeatures(e.point);

    map.getCanvas().style.cursor = features.length ? 'pointer' : '';
});

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


Приоритеты курсора и конфликтующие состояния

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

  • события слоёв (mouseenter, mouseleave)
  • глобальные события (mousemove, pointermove)
  • режимы взаимодействия (drag, select, edit)

Для предотвращения конфликтов используется централизованная функция управления состоянием:

function setCursor(state) {
    map.getCanvas().style.cursor = state;
}

И единая логика:

map.on('mousemove', (e) => {
    if (editModeActive) {
        setCursor('crosshair');
        return;
    }

    const features = map.queryRenderedFeatures(e.point);

    if (features.length) {
        setCursor('pointer');
    } else {
        setCursor('');
    }
});

Курсор для кастомных интерактивных объектов

При отрисовке кастомных слоёв (например, через custom layer или canvas overlay) управление курсором полностью выносится в прикладную логику.

canvas.addEventListener('mousemove', (e) => {
    const hit = hitTestCustomLayer(e);

    map.getCanvas().style.cursor = hit ? 'pointer' : 'default';
});

Такая схема применяется при интеграции с нестандартной графикой и WebGL-слоями.


Оптимизация частоты обновления курсора

При частых событиях mousemove или pointermove изменение курсора может вызывать лишние перерисовки DOM-свойств. Для оптимизации применяется проверка текущего состояния:

let currentCursor = '';

function updateCursor(next) {
    if (currentCursor !== next) {
        currentCursor = next;
        map.getCanvas().style.cursor = next;
    }
}

Это уменьшает количество операций записи в DOM и повышает стабильность интерфейса при высокой частоте событий.


Типовые значения курсора в MapLibre GL JS

Чаще всего используются стандартные CSS-значения:

  • default — базовое состояние
  • pointer — интерактивный элемент
  • grab — возможность перетаскивания
  • grabbing — активное перетаскивание
  • crosshair — режим выбора/прицеливания
  • not-allowed — запрещённое действие
  • wait — ожидание обработки

Синхронизация курсора с состоянием слоёв

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

map.addLayer({
    id: 'buildings',
    type: 'fill',
    source: 'data',
    paint: {
        'fill-color': '#888'
    }
});

map.on('mouseenter', 'buildings', () => {
    map.getCanvas().style.cursor = 'pointer';
});

map.on('mouseleave', 'buildings', () => {
    map.getCanvas().style.cursor = '';
});

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