В 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 и агрегируют информацию от всех
активных слоёв, участвующих в интерактивности.
Интерактивность в 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.
Система работает следующим образом:
info объект с данными о попаданииgetCursorХотя курсор может управляться глобально через 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 можно задать поведение курсора
глобально, используя метод setProps. Это позволяет
динамически изменять логику без пересоздания сцены.
deckgl.setProps({
getCursor: ({isHovering}) => {
return isHovering ? 'crosshair' : 'default';
}
});
Такой подход удобен при изменении режима приложения, например:
При использовании 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.
Приоритет определяется:
object под курсоромonHover,
onClick)getCursorЕсли хотя бы один слой возвращает объект в onHover,
состояние isHovering становится истинным, даже если другие
слои не содержат данных в данной точке.
Для сложных интерфейсов применяется прямой анализ
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 по всем
слоям, предоставляя доступ к деталям каждого объекта. Это позволяет
учитывать тип слоя, его идентификатор и свойства данных.
Deck.gl рендерит сцену через WebGL canvas, и курсор фактически применяется к DOM-элементу контейнера. Поэтому изменение курсора может быть выполнено и через CSS:
.deck-canvas {
cursor: grab;
}
Однако такой подход конфликтует с динамическими состояниями picking, так как CSS не учитывает взаимодействие слоёв. Поэтому CSS-курсор используется только как базовое значение, а логика взаимодействия переопределяет его через JavaScript.
Особое значение имеет состояние 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 может использоваться для подсветки объектов, тогда как курсор отражает тип возможного действия.
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-деталью слоя.