Для использования Interact.js в React-проекте сначала необходимо установить библиотеку через npm:
npm install interactjs
После установки импортируется основной объект
interact:
import interact from 'interactjs';
Важно помнить, что React управляет виртуальным DOM, поэтому любые
прямые манипуляции с элементами DOM через Interact.js должны учитывать
жизненный цикл компонентов. Наиболее корректным способом инициализации
взаимодействий является использование хуков useEffect для
функциональных компонентов или методов жизненного цикла в классовых
компонентах.
Для создания перетаскиваемого элемента используется метод
interact с цепочкой вызовов:
import { useEffect, useRef } from 'react';
import interact from 'interactjs';
function DraggableBox() {
const boxRef = useRef(null);
useEffect(() => {
interact(boxRef.current)
.draggable({
listeners: {
start(event) {
console.log('Drag start', event);
},
move(event) {
const target = event.target;
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
},
end(event) {
console.log('Drag end', event);
}
}
});
}, []);
return <div ref={boxRef} style={{ width: 100, height: 100, background: 'steelblue' }} />;
}
Ключевые моменты:
useRef позволяет получить доступ к
реальному DOM-элементу без конфликта с виртуальным DOM.data-x и data-y служат для
хранения текущей позиции элемента.start, move, end
позволяют отслеживать жизненный цикл перетаскивания.Для изменения размера элемента добавляется метод
resizable:
interact(boxRef.current)
.resizable({
edges: { left: true, right: true, bottom: true, top: true },
listeners: {
move(event) {
let { x, y } = event.target.dataset;
x = parseFloat(x) || 0;
y = parseFloat(y) || 0;
Object.assign(event.target.style, {
width: `${event.rect.width}px`,
height: `${event.rect.height}px`,
transform: `translate(${x + event.deltaRect.left}px, ${y + event.deltaRect.top}px)`
});
event.target.dataset.x = x + event.deltaRect.left;
event.target.dataset.y = y + event.deltaRect.top;
}
},
modifiers: [
interact.modifiers.restrictEdges({
outer: 'parent'
})
],
inertia: true
});
Особенности:
edges определяют, с каких сторон можно изменять
размер.modifiers.restrictEdges ограничивает размеры и
положение элементом-родителем.inertia: true добавляет эффект инерции при отпускании
мыши.Хотя Interact.js напрямую управляет стилями DOM, иногда необходимо синхронизировать данные с состоянием React для реактивного интерфейса:
import { useState, useEffect, useRef } from 'react';
import interact from 'interactjs';
function ControlledBox() {
const boxRef = useRef(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
interact(boxRef.current)
.draggable({
listeners: {
move(event) {
const x = position.x + event.dx;
const y = position.y + event.dy;
boxRef.current.style.transform = `translate(${x}px, ${y}px)`;
setPosition({ x, y });
}
}
});
}, [position]);
return <div ref={boxRef} style={{ width: 120, height: 120, background: 'tomato' }} />;
}
Важные моменты:
state и
обновлять DOM напрямую только через Interact.js во время
drag/resizing.Interact.js позволяет создавать продвинутые пользовательские сценарии:
interact(boxRef.current)
.draggable({
modifiers: [
interact.modifiers.snap({
targets: [interact.createSnapGrid({ x: 50, y: 50 })],
range: Infinity,
relativePoints: [{ x: 0, y: 0 }]
}),
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
],
inertia: true
});
Для динамически создаваемых компонентов важно инициализировать
Interact.js после их рендера. Часто используется useEffect
с зависимостями, отслеживающими массив элементов:
useEffect(() => {
elements.forEach(ref => {
interact(ref.current).draggable({ /* конфигурация */ });
});
}, [elements]);
Это позволяет создавать интерфейсы с любым количеством draggable или resizable блоков без нарушения реактивности.
Interact.js поддерживает жесты pinch, rotate и multi-touch. Их интеграция в React выглядит следующим образом:
interact(boxRef.current)
.gesturable({
listeners: {
move(event) {
const scale = (parseFloat(event.target.dataset.scale) || 1) * (1 + event.ds);
event.target.style.transform = `scale(${scale})`;
event.target.dataset.scale = scale;
}
}
});
Особенности:
gesturable позволяет обрабатывать pinch, rotate и
другие мультитач-события.event.ds — изменение масштаба за текущее событие.dataset удобно для хранения текущих
параметров трансформации.Interact.js позволяет комбинировать draggable, resizable и gesturable на одном элементе:
interact(boxRef.current)
.draggable({ inertia: true })
.resizable({ edges: { left: true, right: true, bottom: true, top: true } })
.gesturable();
Важно правильно обрабатывать transform, чтобы сочетание
масштабирования, вращения и перемещения не конфликтовало. Обычно
хранение состояния трансформации в dataset с раздельными
полями x, y, scale,
angle обеспечивает корректное комбинирование действий.
requestAnimationFrame внутри обработчиков move
для сложных интерфейсов.interact(boxRef.current).unset().modifiers для ограничения движения и
размеров вместо контроля через React-состояние.useInteract для унификации конфигурации.