Управление курсором

В Deck.gl управление курсором тесно связано с системой picking и событиями взаимодействия слоёв. Курсор рассматривается не как отдельный элемент DOM, а как производное состояния сцены: наличие интерактивных объектов под указателем, тип события и текущая активность слоёв определяют его визуальный вид.

Базовый механизм основан на функции getCursor, которая вычисляет CSS-значение курсора на каждом кадре рендера или при изменении состояния взаимодействия.

const deckgl = new Deck({
  initialViewState: {
    longitude: 0,
    latitude: 0,
    zoom: 2
  },
  controller: true,

  getCursor: ({isHovering, isDragging}) => {
    if (isDragging) return 'grabbing';
    if (isHovering) return 'pointer';
    return 'grab';
  }
});

Состояния isHovering и isDragging формируются внутренней системой Deck.gl и агрегируют информацию от всех активных слоёв, участвующих в интерактивности.


Парадигма picking и роль курсора

Интерактивность в Deck.gl строится вокруг picking engine, который определяет объекты под курсором мыши. Каждый слой, поддерживающий взаимодействие, должен быть помечен как pickable: true.

import {ScatterplotLayer} from '@deck.gl/layers';

const layer = new ScatterplotLayer({
  id: 'points',
  data,
  pickable: true,
  getPosition: d => d.position,
  getRadius: 10000,
  getFillColor: [255, 140, 0]
});

Когда pickable включён, слой начинает участвовать в вычислении pickingInfo. Это напрямую влияет на значение isHovering, которое затем используется в getCursor.

Система работает следующим образом:

  • движок выполняет hit-test при движении мыши
  • формируется info объект с данными о попадании
  • агрегируется состояние hover по всем слоям
  • обновляется курсор через getCursor

События onHover и динамическое управление курсором

Хотя курсор может управляться глобально через getCursor, более точный контроль осуществляется на уровне слоя через onHover.

const layer = new ScatterplotLayer({
  id: 'interactive-points',
  data,
  pickable: true,

  onHover: info => {
    if (info.object) {
      document.body.style.cursor = 'pointer';
    } else {
      document.body.style.cursor = 'default';
    }
  }
});

Такой подход применяется, когда требуется переопределить глобальную логику или добавить специфическое поведение для отдельных слоёв.

Объект info содержит ключевые поля:

  • object — выбранный элемент данных
  • x, y — координаты указателя
  • coordinate — географические координаты
  • layer — слой-источник события

Манипуляция DOM-курсорa напрямую часто используется в интеграции с внешними UI-фреймворками, однако в чистом Deck.gl предпочтение отдаётся getCursor.


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

В экземпляре Deck можно задать поведение курсора глобально, используя метод setProps. Это позволяет динамически изменять логику без пересоздания сцены.

deckgl.setProps({
  getCursor: ({isHovering}) => {
    return isHovering ? 'crosshair' : 'default';
  }
});

Такой подход удобен при изменении режима приложения, например:

  • переключение между режимами редактирования и просмотра
  • активация инструментов измерения
  • временное отключение интерактивности

React-интеграция и управление курсором в DeckGL компоненте

При использовании React обёртки DeckGL управление курсором становится декларативным. Свойство getCursor передаётся как пропс и пересчитывается при изменении состояния компонента.

import DeckGL from '@deck.gl/react';

function MapView({mode}) {
  return (
    <DeckGL
      initialViewState={{
        longitude: 10,
        latitude: 50,
        zoom: 4
      }}
      controller={true}
      layers={[layer]}

      getCursor={({isHovering, isDragging}) => {
        if (mode === 'edit') return 'crosshair';
        if (isDragging) return 'grabbing';
        return isHovering ? 'pointer' : 'grab';
      }}
    />
  );
}

React-подход усиливает роль состояния приложения: курсор становится функцией UI-режима, а не только реакции на геометрические события.


Приоритеты курсора при конфликте слоёв

Когда несколько слоёв одновременно активны и поддерживают pickable, возникает ситуация конкуренции за управление курсором. Deck.gl решает её через агрегированное состояние picking.

Приоритет определяется:

  1. наличием object под курсором
  2. верхним слоем в порядке рендеринга
  3. активными обработчиками событий (onHover, onClick)
  4. глобальной функцией getCursor

Если хотя бы один слой возвращает объект в onHover, состояние isHovering становится истинным, даже если другие слои не содержат данных в данной точке.


Кастомные стратегии курсора через анализ pickingInfo

Для сложных интерфейсов применяется прямой анализ pickingInfo, позволяющий строить многоуровневую логику курсора.

getCursor: ({hoveredObjects, isDragging}) => {
  if (isDragging) return 'grabbing';

  const hasEditable = hoveredObjects?.some(o => o.layer.id === 'edit-layer');
  if (hasEditable) return 'crosshair';

  const hasClickable = hoveredObjects?.length > 0;
  if (hasClickable) return 'pointer';

  return 'default';
}

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


Интеграция с DOM и canvas-уровнем

Deck.gl рендерит сцену через WebGL canvas, и курсор фактически применяется к DOM-элементу контейнера. Поэтому изменение курсора может быть выполнено и через CSS:

.deck-canvas {
  cursor: grab;
}

Однако такой подход конфликтует с динамическими состояниями picking, так как CSS не учитывает взаимодействие слоёв. Поэтому CSS-курсор используется только как базовое значение, а логика взаимодействия переопределяет его через JavaScript.


Drag-состояние и визуальная обратная связь

Особое значение имеет состояние isDragging, которое активируется при взаимодействии с controller: true. В этом режиме Deck.gl отслеживает начало и конец жеста перемещения.

getCursor: ({isDragging}) => {
  return isDragging ? 'grabbing' : 'grab';
}

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


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

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

class CustomController {
  handleEvent(event) {
    if (event.type === 'pointerdown') {
      this.setCursor('grabbing');
    }
    if (event.type === 'pointerup') {
      this.setCursor('grab');
    }
  }
}

В таких сценариях курсор синхронизируется с состоянием контроллера, а не с picking системой. Это особенно важно при реализации кастомных навигационных схем.


Разделение hover-логики и курсора

В сложных приложениях hover-состояние и курсор часто разделяются. Hover может использоваться для подсветки объектов, тогда как курсор отражает тип возможного действия.

  • hover: визуальная подсветка слоя
  • cursor: сигнализация действия (клик, перетаскивание, редактирование)
onHover: info => {
  if (info.object) {
    setHighlight(info.object.id);
  }
}
getCursor: ({isHovering}) => {
  return isHovering ? 'pointer' : 'default';
}

Такое разделение снижает перегрузку интерфейса и делает поведение предсказуемым.


Переопределение курсора через внешние состояния приложения

Курсор в Deck.gl часто зависит не только от сцены, но и от глобального состояния приложения: активных инструментов, выбранных режимов или текущих операций редактирования.

getCursor: ({isHovering}) => {
  switch (appMode) {
    case 'measure':
      return 'crosshair';
    case 'pan':
      return isHovering ? 'grab' : 'grab';
    case 'select':
      return isHovering ? 'pointer' : 'default';
    default:
      return 'default';
  }
}

Такой подход делает курсор частью архитектуры состояния, а не локальной UI-деталью слоя.