Перетаскивание в интерфейсе строится на непрерывном преобразовании координат указателя в трансформацию элемента. В основе лежит простая модель: фиксируется точка начала взаимодействия, затем вычисляется смещение между текущим положением курсора и стартовой позицией, после чего это смещение применяется к стилю элемента.
Ключевые компоненты поведения:
translate к элементуБиблиотека Popmotion предоставляет набор низкоуровневых инструментов, из которых собирается полноценное перетаскивание без необходимости использовать громоздкие абстракции.
Основой взаимодействия является поток событий указателя. Popmotion инкапсулирует работу с мышью и touch через единый механизм, предоставляя унифицированный поток координат.
import { pointer } from 'popmotion';
const trackPointer = pointer({
x: 0,
y: 0
});
trackPointer.start((v) => {
// v.x и v.y — текущие координаты указателя
});
Каждое обновление содержит абсолютные координаты. Для реализации drag этого недостаточно — требуется преобразование в относительное смещение.
Для управления DOM Popmotion использует styler, который
абстрагирует работу с transform, x,
y и другими свойствами.
import { styler } from 'popmotion';
const element = document.querySelector('.box');
const boxStyler = styler(element);
styler позволяет изменять позицию без прямого обращения
к style.transform, сохраняя внутреннее состояние
координат.
Логика drag строится вокруг трёх этапов:
import { pointer, styler } from 'popmotion';
const element = document.querySelector('.box');
const box = styler(element);
let startX = 0;
let startY = 0;
const drag = pointer({ x: 0, y: 0 });
drag.start((v) => {
startX = v.x;
startY = v.y;
});
На этом этапе фиксируется базовая точка взаимодействия.
Основная формула движения:
offset = currentPointerPosition - startPointerPosition + initialElementPosition
Применение в коде:
let baseX = 0;
let baseY = 0;
drag.start((start) => {
baseX = start.x;
baseY = start.y;
});
drag.update((v) => {
const x = baseX + (v.x - baseX);
const y = baseY + (v.y - baseY);
box.set({
x,
y
});
});
Однако такая запись часто упрощается, поскольку Popmotion
предоставляет более удобные механизмы управления состоянием через
value.
value используется для хранения текущих координат с
реактивным обновлением.
import { value, pointer } from 'popmotion';
const position = value({ x: 0, y: 0 });
const drag = pointer(position.get());
Состояние становится источником истины: каждое движение обновляет
value, а UI подписан на изменения.
Наиболее распространённая схема связывает поток указателя с состоянием позиции.
import { pointer, value, styler } from 'popmotion';
const element = document.querySelector('.box');
const box = styler(element);
const position = value({ x: 0, y: 0 }, (v) => {
box.set(v);
});
let origin = { x: 0, y: 0 };
const drag = pointer(position.get());
drag.start((start) => {
origin = position.get();
});
drag.update((v) => {
position.update({
x: origin.x + v.x - drag.origin.x,
y: origin.y + v.y - drag.origin.y
});
});
Такая модель позволяет отделить вычисления от рендеринга.
Часто требуется ограничить область движения элемента. Это достигается через ручную проверку координат перед применением значения.
const clamp = (min, max) => (v) => Math.max(min, Math.min(v, max));
const xClamp = clamp(0, 300);
const yClamp = clamp(0, 500);
drag.update((v) => {
position.update({
x: xClamp(origin.x + v.x - drag.origin.x),
y: yClamp(origin.y + v.y - drag.origin.y)
});
});
Ограничения применяются на уровне модели, а не DOM, что сохраняет предсказуемость поведения.
После завершения drag часто добавляется инерционное движение. Popmotion предоставляет физические утилиты для моделирования замедления.
import { decay } from 'popmotion';
drag.end((v) => {
decay({
from: position.get().x,
velocity: v.velocity ? v.velocity.x : 0
}).start((latest) => {
box.set({ x: latest });
});
});
Инерция формирует ощущение естественного движения за счёт постепенного снижения скорости.
Иногда drag строится напрямую через события DOM без pointer-абстракции.
import { listen } from 'popmotion';
listen(element, 'mousedown').start(() => {
// начало drag
});
listen(document, 'mousemove').start((e) => {
// движение
});
listen(document, 'mouseup').start(() => {
// завершение
});
Такой подход полезен при необходимости тонкого контроля над фазами взаимодействия.
Popmotion оптимизирует работу через transform, что
позволяет задействовать GPU-композицию.
Основные свойства:
x → translateXy → translateYbox.set({
x: 120,
y: 80
});
Эти значения транслируются в:
transform: translateX(120px) translateY(80px)
При поддержке touch устройств pointer автоматически выбирает активный контакт. Однако для сложных сценариев требуется отслеживание идентификаторов pointer events.
const activePointers = new Map();
listen(element, 'pointerdown').start((e) => {
activePointers.set(e.pointerId, { x: e.pageX, y: e.pageY });
});
Это позволяет реализовать многопальцевые жесты и независимые drag-сессии.
После завершения перемещения часто применяется возврат в исходное состояние с помощью пружины.
import { spring } from 'popmotion';
drag.end(() => {
spring({
from: position.get().x,
to: 0,
stiffness: 200,
damping: 20
}).start((v) => {
box.set({ x: v });
});
});
Пружинная модель создаёт физически правдоподобное возвращение элемента.
В более сложных интерфейсах движение разделяется по осям и может управляться независимо.
const position = value({ x: 0, y: 0 });
drag.update((v) => {
const dx = v.x - drag.origin.x;
const dy = v.y - drag.origin.y;
position.update({
x: origin.x + dx,
y: origin.y + dy * 0.5
});
});
Такое разделение позволяет создавать параллакс-эффекты и ограниченные траектории движения.
Перетаскивание в Popmotion можно рассматривать как поток:
pointer events → нормализация координат → вычисление delta → обновление state → styler → DOM
Каждое звено цепи изолировано и может быть заменено, не нарушая общую структуру взаимодействия.
Сессия перетаскивания включает чёткие стадии:
let dragging = false;
drag.start(() => dragging = true);
drag.end(() => dragging = false);
Такое состояние позволяет координировать дополнительные эффекты: подсветку, тени, масштабирование.
Описанный подход остаётся одинаковым независимо от сложности интерфейса. Поведение определяется только трансформациями состояния, а не DOM-логикой. Это позволяет расширять drag-систему до:
Все сценарии строятся на одном базовом механизме — преобразовании потока координат в управляемое состояние элемента