Кастомные обработчики событий

Взаимодействие в Deck.gl строится поверх системы picking’а WebGL и единого обработчика событий, который агрегирует события мыши и сенсора, преобразуя их в семантические события уровня «объект сцены».

Ключевая особенность архитектуры заключается в том, что события не привязаны напрямую к DOM-элементам, как в классических интерфейсах, а вычисляются через определение попадания курсора в геометрические примитивы, отрисованные слоями.

Событийная модель включает три уровня:

  • DOM-события (mousemove, click, pointermove)
  • слой интерпретации Deck.gl (DeckGL container)
  • слой picking-логики (GPU/CPU идентификация объектов)

Включение интерактивности слоя

Любой слой в Deck.gl по умолчанию не участвует в системе событий. Для активации требуется явное указание возможности «пикаться».

Основное свойство:

pickable: true

Без этого параметра слой игнорируется системой picking’а и не генерирует события.

Дополнительные свойства, влияющие на интерактивность:

autoHighlight: true
highlightColor: [255, 255, 0, 80]

autoHighlight включает автоматическую подсветку объекта под курсором, а highlightColor задаёт визуальный эффект выделения.


Глобальные обработчики DeckGL

Компонент верхнего уровня DeckGL принимает обработчики событий, агрегирующие взаимодействие со всеми слоями.

Основные события:

<DeckGL
  layers={[layer]}
  onCl ick={info => {}}
  onHo ver={info => {}}
  onDragSt art={info => {}}
  onD rag={info => {}}
  onDrag End={info => {}}
/>

Каждый обработчик получает единый объект info, содержащий контекст взаимодействия.


Структура объекта события

Объект события является центральной частью всей системы кастомных обработчиков.

Типичная структура:

{
  x: 120,
  y: 340,
  coordinate: [37.78, -122.41],
  object: { ... },
  layer: LayerInstance,
  sourceLayer: LayerInstance,
  index: 12,
  pixel: [120, 340],
  color: [0, 0, 0, 255],
  picked: true
}

Основные поля:

  • x, y — координаты курсора в canvas
  • coordinate — географические координаты (если используется map projection)
  • object — объект данных, ассоциированный с элементом
  • layer — слой, инициировавший событие
  • index — индекс объекта в массиве данных слоя
  • picked — результат picking-операции

Обработчики на уровне слоя

Помимо глобальных событий DeckGL, каждый слой может иметь собственные обработчики.

Пример:

new ScatterplotLayer({
  id: 'points',
  data,
  pickable: true,

  onHover: (info, event) => {
    console.log(info.object);
  },

  onClick: (info, event) => {
    console.log('clicked point', info.object);
  }
});

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


Приоритет обработки событий

При наличии нескольких уровней обработчиков действует следующая последовательность:

  1. Обработчик слоя (layer-level)
  2. Обработчик DeckGL (global-level)
  3. Системные действия контроллера (pan/zoom)

Если событие обработано на уровне слоя и не предотвращено, оно продолжает распространяться вверх.


Механизм picking’а

Deck.gl использует GPU-based picking, где каждый объект рендерится в offscreen framebuffer с уникальным цветовым идентификатором.

Алгоритм:

  1. Каждый объект получает уникальный pick color
  2. Рендер происходит в скрытый буфер
  3. По координате курсора считывается пиксель
  4. Цвет декодируется в индекс объекта

Это позволяет:

  • работать с миллионами объектов
  • избегать DOM hit-testing
  • обеспечивать стабильную производительность

Кастомные обработчики через расширение DeckGL

Расширение стандартной модели достигается через композицию событий.

Пример перехвата всех событий:

function handleInteraction(info) {
  if (!info.picked) return;

  const { layer, object } = info;

  if (layer.id === 'points') {
    processPoint(object);
  }

  if (layer.id === 'heatmap') {
    processHeatmapCell(object);
  }
}

Использование:

<DeckGL
  layers={layers}
  onHo ver={handleInteraction}
  onCl ick={handleInteraction}
/>

Разделение логики событий и состояния приложения

Кастомные обработчики часто интегрируются с внешним состоянием (Redux, Zustand, MobX).

Типовая схема:

onHover: (info) => {
  if (info.picked) {
    setTooltip({
      x: info.x,
      y: info.y,
      data: info.object
    });
  } else {
    setTooltip(null);
  }
}

Важно учитывать, что частота onHover может быть высокой, особенно при движении мыши, что требует оптимизации обновлений состояния.


Оптимизация обработчиков событий

Основная проблема кастомных обработчиков — избыточная частота вызовов.

Типовые стратегии оптимизации:

Throttling

let lastCall = 0;

function onHover(info) {
  const now = Date.now();
  if (now - lastCall < 50) return;
  lastCall = now;

  handleHover(info);
}

Debouncing

let timeout;

function onHover(info) {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    handleHover(info);
  }, 100);
}

Фильтрация по слоям

onHover: (info) => {
  if (info.layer.id !== 'interactive-layer') return;
  process(info);
}

Кастомные события через расширение Layer

Для сложных сценариев создаётся наследник Layer, который переопределяет поведение interaction hooks.

import { Layer } from '@deck.gl/core';

class CustomLayer extends Layer {
  getPickingInfo({ info }) {
    if (info.picked) {
      info.customFlag = true;
    }
    return info;
  }
}

Такой подход позволяет внедрять дополнительную бизнес-логику на уровне инфраструктуры слоя.


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

Deck.gl поддерживает Pointer Events API, что обеспечивает унификацию мыши, сенсора и пера.

События:

  • pointerdown
  • pointermove
  • pointerup

Это важно для мобильных сценариев и устройств с несколькими источниками ввода.


Интеграция кастомных tooltip и overlay систем

Частый сценарий кастомных обработчиков — отображение tooltip.

onHover: ({ x, y, object }) => {
  if (object) {
    tooltip.update({
      position: [x, y],
      content: object.name
    });
  }
}

Отрисовка происходит вне WebGL-контекста, обычно через HTML overlay.


Контекст события и работа с камерой

События DeckGL тесно связаны с состоянием камеры.

В info может быть доступ к координатам, уже преобразованным в систему мира:

coordinate: [lng, lat]

При кастомной обработке важно учитывать трансформации:

  • projection
  • zoom level
  • pitch/rotation камеры

Обработка drag-сценариев

Drag-события представляют отдельную категорию взаимодействий:

onDragStart: info => {},
onDrag: info => {},
onDragEnd: info => {}

Они позволяют реализовывать:

  • перемещение объектов
  • редактирование геометрии
  • интерактивное рисование

Структура info при drag сохраняет объект контекста, обеспечивая непрерывность состояния.


Конфликты между событиями слоя и контроллера

Deck.gl использует controller для управления камерой (pan/zoom/rotate).

При включённой интерактивности возможны конфликты:

  • drag объекта vs pan карты
  • click объекта vs click map

Решение:

onDragStart: (info, event) => {
  event.stopPropagation();
}

или через отключение контроллера:

controller: {
  dragPan: false
}

Использование кастомного hit detection

В некоторых сценариях GPU picking недостаточен. Тогда применяется ручная логика:

onClick: ({ x, y }) => {
  const result = customSpatialIndex.query(x, y);
  process(result);
}

Это характерно для:

  • больших тайловых сеток
  • кастомных кластеров
  • гибридных визуализаций

Асинхронные обработчики событий

Deck.gl допускает асинхронную обработку:

onClick: async (info) => {
  const data = await fetchDetails(info.object.id);
  updateState(data);
}

Важно учитывать, что частые async вызовы могут приводить к race conditions при движении курсора.


Централизованный event dispatcher

Для сложных приложений часто вводится слой абстракции:

class EventManager {
  constructor(store) {
    this.store = store;
  }

  handleHover = (info) => {
    this.store.dispatch({
      type: 'HOVER_OBJECT',
      payload: info.object
    });
  };
}

DeckGL использует этот слой как адаптер между WebGL-событиями и архитектурой приложения.


Переопределение поведения picking на уровне слоя

Некоторые слои позволяют переопределить поведение выбора объекта:

getPickingInfo({ info, mode }) {
  if (mode === 'hover') {
    return info;
  }
  return null;
}

Это используется для:

  • ограничения интерактивности
  • фильтрации объектов
  • кастомных правил выбора

Системные ограничения событийной модели

События Deck.gl имеют несколько ограничений:

  • высокая частота требует оптимизации
  • GPU picking может давать задержку
  • сложные сцены увеличивают latency
  • взаимодействие зависит от структуры слоёв

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