В основе работы с жестами в Popmotion лежит принцип отслеживания указателя и преобразования его движения в поток числовых значений. Любой жест — это комбинация координат, скорости, направления и времени. Библиотека не навязывает готовую систему gesture recognition, вместо этого предоставляет низкоуровневые инструменты для построения собственной логики взаимодействия.
Ключевым элементом становится работа с указателем (pointer), который объединяет мышь, сенсорные касания и стилус в единый интерфейс событий.
Popmotion предоставляет функцию pointer, которая
преобразует DOM-события в поток координат. Это позволяет работать не с
событиями как таковыми, а с непрерывным движением.
import { pointer, value } from 'popmotion';
const x = value(0);
const y = value(0);
pointer({
onMove: (v) => {
x.set(v.x);
y.set(v.y);
}
}).start();
Каждое обновление содержит текущие координаты указателя относительно viewport. Это становится базой для построения drag, swipe и любых других жестов.
Перетаскивание (drag) строится на разнице между начальной точкой и текущим положением указателя. Для хранения стартовой позиции фиксируется offset, который затем используется для вычисления смещения.
import { pointer, value } from 'popmotion';
const position = value({ x: 0, y: 0 });
let startX = 0;
let startY = 0;
pointer({
onStart: (v) => {
startX = position.get().x;
startY = position.get().y;
},
onMove: (v) => {
position.set({
x: startX + v.x - v.startX,
y: startY + v.y - v.startY
});
}
}).start();
Такой подход отделяет визуальное состояние от событийной модели и позволяет легко добавлять физику поверх базового перемещения.
Свайп отличается от обычного drag наличием высокой скорости и короткого времени взаимодействия. Popmotion предоставляет утилиты для вычисления velocity, которые используются для определения характера движения.
Основные параметры свайпа:
Простейшая логика определения свайпа:
import { pointer, velocity } from 'popmotion';
let points = [];
pointer({
onMove: (v) => {
points.push({ x: v.x, time: Date.now() });
if (points.length > 10) points.shift();
},
onEnd: () => {
const first = points[0];
const last = points[points.length - 1];
const dx = last.x - first.x;
const dt = last.time - first.time;
const v = dx / dt;
const isSwipe = Math.abs(v) > 0.5;
const direction = v > 0 ? 'right' : 'left';
}
}).start();
Свайп определяется не только расстоянием, но и отношением расстояния ко времени, что делает поведение естественным и приближенным к физике взаимодействия.
Любой жест требует фильтрации шумов. Даже минимальные движения пальца могут интерпретироваться как начало свайпа или drag. Для этого вводятся пороговые значения:
const SWIPE_THRESHOLD = 100;
const TIME_THRESHOLD = 300;
pointer({
onEnd: (v) => {
const dx = v.x - v.startX;
const dt = Date.now() - v.startTime;
if (Math.abs(dx) > SWIPE_THRESHOLD && dt < TIME_THRESHOLD) {
const direction = dx > 0 ? 'right' : 'left';
}
}
}).start();
Такая фильтрация отделяет намеренные свайпы от случайных движений.
Popmotion включает физические модели, позволяющие имитировать
инерционное движение после завершения жеста. Для этого используется
функция physics или tween в сочетании с
вычисленной скоростью.
Инерция создаёт эффект продолжения движения с постепенным замедлением.
import { physics } from 'popmotion';
physics({
from: position.get().x,
velocity: 1200,
friction: 0.8
}).start((v) => {
position.set(v);
});
Параметр friction контролирует скорость затухания. Чем меньше значение, тем быстрее останавливается движение.
Жесты редко существуют без ограничений. Часто требуется удерживать элемент внутри заданного диапазона. Это реализуется через clamp — ограничение значений.
const clamp = (min, max) => (v) => Math.min(Math.max(v, min), max);
const limitX = clamp(0, 300);
pointer({
onMove: (v) => {
position.set({
x: limitX(v.x),
y: position.get().y
});
}
}).start();
Ограничения применяются либо к координатам, либо к скорости, в зависимости от логики интерфейса.
Жесты в Popmotion часто связываются с состоянием UI-элемента через
value. Это позволяет синхронизировать анимацию и
пользовательский ввод.
import { value } from 'popmotion';
const card = value({ x: 0, rotate: 0 });
pointer({
onMove: (v) => {
card.set({
x: v.x,
rotate: v.x / 10
});
}
}).start();
Поворот элемента на основе смещения создаёт эффект физического взаимодействия с объектом.
Определение направления является ключевой частью gesture logic. Обычно используется сравнение абсолютных значений по осям.
const getDirection = (dx, dy) => {
if (Math.abs(dx) > Math.abs(dy)) {
return dx > 0 ? 'right' : 'left';
}
return dy > 0 ? 'down' : 'up';
};
Такой подход позволяет различать горизонтальные и вертикальные жесты, что важно для сложных интерфейсов с несколькими типами взаимодействий.
Хотя Popmotion работает преимущественно с pointer events, мульти-тач жесты реализуются через отслеживание нескольких указателей одновременно. Каждый pointer имеет уникальный identifier, который используется для различения касаний.
const pointers = new Map();
window.addEventListener('pointerdown', (e) => {
pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
});
window.addEventListener('pointermove', (e) => {
if (!pointers.has(e.pointerId)) return;
pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
});
На основе этих данных можно вычислять pinch, rotate и другие комбинированные жесты.
В реальных интерфейсах drag и swipe не разделяются строго. Один и тот же жест может интерпретироваться по-разному в зависимости от скорости завершения.
Логика разделения обычно строится на:
const isSwipe = (dx, dt, vx) => {
return Math.abs(vx) > 0.6 && dt < 250 && Math.abs(dx) > 80;
};
Если условия не выполняются, жест трактуется как обычное перемещение.
Свайпы часто используются для управления переходами между состояниями интерфейса. После завершения жеста вычисляется итоговая позиция, к которой элемент должен анимироваться.
import { tween } from 'popmotion';
tween({
from: position.get().x,
to: direction === 'left' ? -300 : 300,
duration: 200
}).start(position.set);
Такой подход создаёт ощущение завершённости взаимодействия и закрепляет результат жеста.
Чувствительность интерфейса регулируется коэффициентами масштабирования входных данных. Это позволяет адаптировать поведение под разные устройства.
const sensitivity = 0.8;
pointer({
onMove: (v) => {
position.set({
x: v.x * sensitivity,
y: v.y * sensitivity
});
}
}).start();
На сенсорных экранах обычно используется меньшая чувствительность для более плавного взаимодействия.
Сильная сторона Popmotion заключается в возможности сочетать прямое управление и физические модели. После завершения жеста управление передаётся инерции, а при новом касании физика прерывается и снова подключается pointer control.
Такой переход создаёт непрерывность взаимодействия, где жест и анимация становятся единой системой движения.