Angular интеграция

Для работы с Interact.js в Angular проекте требуется сначала установить библиотеку через npm:

npm install interactjs

После установки библиотеку можно импортировать в компонент или сервис Angular:

import interact from 'interactjs';

Важно учитывать, что Interact.js работает напрямую с DOM-элементами, поэтому инициализацию взаимодействий лучше выполнять в хуках жизненного цикла Angular, таких как ngAfterViewInit, чтобы убедиться, что элементы уже присутствуют в DOM.


Настройка drag-and-drop элементов

Создание перетаскиваемого элемента осуществляется следующим образом:

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 — изменение угла поворота.
  • Хранение состояния через атрибуты DOM позволяет сохранять непрерывное преобразование между событиями.

Настройка resizable элементов

Для изменения размера элемента используется метод 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 Template References

Для интеграции с Angular удобнее использовать @ViewChild для прямого доступа к DOM-элементам:

@ViewChild('dragItem') dragItem!: ElementRef;

ngAfterViewInit() {
  interact(this.dragItem.nativeElement)
    .draggable({
      listeners: { move: this.dragMoveListener }
    });
}

Такой подход исключает зависимости от классов CSS и обеспечивает прямую привязку к конкретному компоненту. Это особенно важно в динамически создаваемых списках или при использовании *ngFor.


События Interact.js в Angular

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 или вызова методов сервиса.


Производительность и Angular Change Detection

Interact.js напрямую модифицирует DOM, поэтому рекомендуется использовать NgZone для предотвращения излишнего срабатывания Angular change detection:

constructor(private ngZone: NgZone) {}

ngAfterViewInit() {
  this.ngZone.runOutsideAngular(() => {
    interact('.draggable')
      .draggable({ listeners: { move: this.dragMoveListener } });
  });
}

Пояснение:

  • runOutsideAngular предотвращает лишние проверки изменений при каждом событии move.
  • Обновление Angular состояния выполняется отдельно через ngZone.run(...) только при необходимости.

Комбинирование функций: drag, resize и gesturable

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 } });

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


Интеграция с Angular Reactive Forms и состояниями

Для хранения позиции, размера и угла элементов можно использовать реактивные формы или сервис состояния:

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)`;
}

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


Особенности Angular + Interact.js

  • Поддержка динамически созданных элементов требует повторной инициализации Interact.js после добавления DOM-узлов.
  • В сочетании с *ngFor рекомендуется использовать trackBy для сохранения ссылок на элементы и предотвращения разрушения привязки.
  • Использование Renderer2 совместно с Interact.js минимизирует прямое вмешательство в DOM, но при этом требует аккуратной работы с трансформациями.