Pointer events API

Pointer events в Popmotion построены вокруг унифицированной модели обработки ввода, которая абстрагирует мышь, сенсорные экраны и стилусы в единый поток координатных событий. В основе лежит реактивная модель: движение указателя превращается в последовательность значений, которые можно преобразовывать, комбинировать и связывать с анимациями.

Pointer API в Popmotion предоставляет поток координат x, y, а также дополнительные метаданные события. Внутренне используется подписочная модель, где каждое движение указателя становится новым значением потока.

Ключевая идея — отказ от императивной обработки событий в пользу декларативного описания реакции на ввод.

import { pointer } from 'popmotion';

const pointerTracker = pointer();

pointerTracker.start((v) => {
  console.log(v.x, v.y);
});

Каждое значение v представляет текущее состояние указателя:

{
  x: number,
  y: number,
  deltaX: number,
  deltaY: number,
  velocityX: number,
  velocityY: number,
  pressure?: number,
  point?: number
}

Структура pointer-события

Событие указателя в Popmotion расширяет базовую координатную модель дополнительными параметрами, которые позволяют строить инерционные и физически правдоподобные анимации.

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

  • x, y — абсолютные координаты указателя
  • deltaX, deltaY — смещение относительно предыдущего события
  • velocityX, velocityY — скорость движения по осям
  • pressure — давление (для устройств, поддерживающих stylus)
  • point — идентификатор активного указателя

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

Жизненный цикл pointer stream

Pointer поток проходит три ключевые стадии: старт, обновление и завершение.

Инициация потока

Запуск начинается с фиксации начальной точки:

const pointerTracker = pointer();

pointerTracker.start({
  start: (v) => {
    console.log('start', v.x, v.y);
  }
});

На этом этапе фиксируется начальная позиция и сбрасываются внутренние значения скорости.

Обновление координат

Основной поток значений формируется при движении:

pointerTracker.start({
  update: (v) => {
    element.style.transform = `translate(${v.x}px, ${v.y}px)`;
  }
});

Каждое обновление содержит дифференциальные изменения, что позволяет строить трансформации без дополнительного вычисления дельт.

Завершение взаимодействия

Финальное событие фиксирует завершение жеста:

pointerTracker.start({
  end: (v) => {
    console.log('end velocity:', v.velocityX, v.velocityY);
  }
});

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

Привязка pointer к DOM-элементам

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

const box = document.querySelector('.box');

const tracker = pointer({
  target: box
});

tracker.start((v) => {
  box.style.transform = `translate(${v.x}px, ${v.y}px)`;
});

В этом режиме Popmotion автоматически рассчитывает координаты относительно элемента-цели.

Нормализация координат

Pointer API работает в пикселях, но часто требуется нормализация значений в диапазон 0–1. Это используется для адаптивных интерфейсов и анимационных кривых.

tracker.start((v) => {
  const normalizedX = v.x / window.innerWidth;
  const normalizedY = v.y / window.innerHeight;
});

Нормализованные значения удобно связывать с анимационными функциями.

Многопальцевый ввод

Pointer API поддерживает несколько одновременных указателей через идентификатор point. Это позволяет отслеживать независимые жесты.

tracker.start((v) => {
  if (v.point === 0) {
    // первый палец
  }

  if (v.point === 1) {
    // второй палец
  }
});

Каждый активный pointer сохраняет собственное состояние скорости и координат.

Интеграция с анимациями

Одно из ключевых применений pointer API — управление анимациями в реальном времени. Данные потока напрямую передаются в функции трансформации.

import { animate } from 'popmotion';

const tracker = pointer();

tracker.start((v) => {
  animate({
    from: v.x,
    to: v.x + v.velocityX * 20
  });
});

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

Ограничение движения

Pointer поток можно комбинировать с функциями ограничения значений. Это полезно для drag-интерфейсов.

tracker.start((v) => {
  const x = Math.max(0, Math.min(300, v.x));
  const y = Math.max(0, Math.min(500, v.y));

  element.style.transform = `translate(${x}px, ${y}px)`;
});

Ограничение координат часто используется в интерфейсах перетаскивания и слайдерах.

Отмена и управление подпиской

Pointer поток возвращает объект управления подпиской, позволяющий завершать прослушивание событий.

const subscription = tracker.start((v) => {
  console.log(v.x, v.y);
});

subscription.stop();

Остановка потока освобождает обработчики событий и прекращает вычисление значений.

Комбинирование с трансформациями потоков

Pointer API становится более мощным при использовании вместе с функциями преобразования потоков: фильтрацией, интерполяцией и сглаживанием.

import { pointer, transformMap } from 'popmotion';

const smooth = transformMap({
  x: (v) => v * 0.1,
  y: (v) => v * 0.1
});

pointer().start((v) => {
  const transformed = smooth(v);

  element.style.transform =
    `translate(${transformed.x}px, ${transformed.y}px)`;
});

Такой подход снижает шум движения и делает взаимодействие более плавным.

Поведение скорости и инерции

Velocity в pointer API рассчитывается динамически и отражает реальную скорость перемещения. Это позволяет строить инерционные траектории без дополнительных таймеров.

pointer().start((v) => {
  const projectedX = v.x + v.velocityX * 100;
  const projectedY = v.y + v.velocityY * 100;
});

Скорость обновляется на каждом кадре, что делает данные пригодными для физически корректных анимаций.

Синхронизация с render loop

Pointer поток обычно синхронизируется с requestAnimationFrame внутри Popmotion. Это обеспечивает стабильную частоту обновлений независимо от частоты событий браузера.

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

Поведение при резких движениях

При резких скачках координат Popmotion использует сглаживание значений скорости, чтобы избежать выбросов. Это особенно важно для сенсорных экранов с низкой частотой обновления.

Дельты ограничиваются внутренними фильтрами, что делает движение предсказуемым даже при нестабильном вводе.

Использование в drag-сценариях

Pointer API является основой для реализации drag-and-drop логики. Поток координат напрямую связывается с позиционированием элемента.

pointer().start((v) => {
  element.style.left = `${v.x}px`;
  element.style.top = `${v.y}px`;
});

При этом сохраняется непрерывность движения без ручной обработки событий mousedown, mousemove и mouseup.

Связь с физическими моделями

Pointer данные часто используются как входные параметры для моделей пружин, трения и импульса. Velocity становится основным источником энергии движения, а координаты — состоянием системы.

Такой подход позволяет строить интерфейсы, где движение продолжается после завершения жеста, постепенно затухая под действием коэффициента трения.