Для работы с Interact.js в Angular проекте требуется сначала установить библиотеку через npm:
npm install interactjs
После установки библиотеку можно импортировать в компонент или сервис Angular:
import interact from 'interactjs';
Важно учитывать, что Interact.js работает напрямую с DOM-элементами,
поэтому инициализацию взаимодействий лучше выполнять в хуках жизненного
цикла Angular, таких как ngAfterViewInit, чтобы убедиться,
что элементы уже присутствуют в DOM.
Создание перетаскиваемого элемента осуществляется следующим образом:
ngAfterViewInit() {
interact('.draggable')
.draggable({
inertia: true, // плавное движение после отпускания
modifiers: [
interact.modifiers.restrictRect({
restriction: 'parent',
endOnly: true
})
],
autoScroll: true,
listeners: {
move: this.dragMoveListener
}
});
}
dragMoveListener(event: any) {
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);
}
Ключевые моменты:
inertia добавляет эффект инерции при отпускании
элемента.modifiers.restrictRect ограничивает движение элемента
рамками родителя.autoScroll позволяет странице прокручиваться при
перетаскивании к границам окна.Использование data-x и data-y позволяет
сохранять текущие координаты и корректно обновлять позицию элемента при
каждом событии move.
Interact.js поддерживает жесты масштабирования и поворота через метод
gesturable:
interact('.resizable')
.gesturable({
listeners: {
start: (event) => console.log('Gesture start', event),
move: (event) => this.gestureMoveListener(event)
}
});
gestureMoveListener(event: any) {
const target = event.target;
const scale = (parseFloat(target.getAttribute('data-scale')) || 1) * (1 + event.ds);
const angle = (parseFloat(target.getAttribute('data-angle')) || 0) + event.da;
target.style.transform = `scale(${scale}) rotate(${angle}deg)`;
target.setAttribute('data-scale', scale);
target.setAttribute('data-angle', angle);
}
Особенности работы с жестами:
event.ds — изменение масштаба за один жест.event.da — изменение угла поворота.Для изменения размера элемента используется метод
resizable:
interact('.resizable')
.resizable({
edges: { left: true, right: true, bottom: true, top: true },
listeners: {
move: (event) => this.resizeMoveListener(event)
},
modifiers: [
interact.modifiers.restrictSize({
min: { width: 50, height: 50 },
max: { width: 500, height: 500 }
})
],
inertia: true
});
resizeMoveListener(event: any) {
const target = event.target;
let { width, height } = event.rect;
target.style.width = `${width}px`;
target.style.height = `${height}px`;
const x = (parseFloat(target.getAttribute('data-x')) || 0) + event.deltaRect.left;
const y = (parseFloat(target.getAttribute('data-y')) || 0) + event.deltaRect.top;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute('data-x', x);
target.setAttribute('data-y', y);
}
Примечания:
edges указывает, с каких сторон доступно изменение
размера.restrictSize ограничивает минимальные и максимальные
размеры элемента.deltaRect позволяет корректно перемещать
элемент при изменении размеров с разных сторон.Для интеграции с Angular удобнее использовать @ViewChild
для прямого доступа к DOM-элементам:
@ViewChild('dragItem') dragItem!: ElementRef;
ngAfterViewInit() {
interact(this.dragItem.nativeElement)
.draggable({
listeners: { move: this.dragMoveListener }
});
}
Такой подход исключает зависимости от классов CSS и обеспечивает
прямую привязку к конкретному компоненту. Это особенно важно в
динамически создаваемых списках или при использовании
*ngFor.
Interact.js генерирует несколько типов событий:
dragstart, dragmove, dragend,
resizestart, resizemove,
resizeend, gesturestart,
gesturemove, gestureend.
Пример привязки к событиям с использованием RxJS:
import { fromEvent } from 'rxjs';
ngAfterViewInit() {
const draggable = this.dragItem.nativeElement;
const dragMove$ = fromEvent(draggable, 'dragmove');
dragMove$.subscribe((event: any) => {
this.dragMoveListener(event);
});
}
Использование RxJS позволяет легко интегрировать Interact.js события
в реактивный поток Angular, например для обновления состояния через
BehaviorSubject или вызова методов сервиса.
Interact.js напрямую модифицирует DOM, поэтому рекомендуется
использовать NgZone для предотвращения излишнего
срабатывания Angular change detection:
constructor(private ngZone: NgZone) {}
ngAfterViewInit() {
this.ngZone.runOutsideAngular(() => {
interact('.draggable')
.draggable({ listeners: { move: this.dragMoveListener } });
});
}
Пояснение:
runOutsideAngular предотвращает лишние проверки
изменений при каждом событии move.ngZone.run(...) только при необходимости.Interact.js позволяет комбинировать несколько взаимодействий для одного элемента:
interact('.interactive')
.draggable({ listeners: { move: this.dragMoveListener } })
.resizable({ edges: { left: true, right: true, bottom: true, top: true }, listeners: { move: this.resizeMoveListener } })
.gesturable({ listeners: { move: this.gestureMoveListener } });
Это позволяет создавать полноценные интерактивные панели, виджеты и динамические элементы интерфейса с поддержкой перетаскивания, изменения размеров и жестов.
Для хранения позиции, размера и угла элементов можно использовать реактивные формы или сервис состояния:
form = this.fb.group({
x: [0],
y: [0],
width: [100],
height: [100],
scale: [1],
angle: [0]
});
dragMoveListener(event: any) {
const x = (this.form.value.x || 0) + event.dx;
const y = (this.form.value.y || 0) + event.dy;
this.form.patchValue({ x, y }, { emitEvent: false });
event.target.style.transform = `translate(${x}px, ${y}px)`;
}
Использование реактивной формы позволяет связывать координаты и размеры с другими компонентами, таблицами, сохранением в базу данных и серверной синхронизацией.
*ngFor рекомендуется использовать
trackBy для сохранения ссылок на элементы и предотвращения
разрушения привязки.Renderer2 совместно с Interact.js
минимизирует прямое вмешательство в DOM, но при этом требует аккуратной
работы с трансформациями.