Snap-эффекты и привязка к сетке

Snap-эффекты позволяют элементам автоматически “прилипать” к определённым координатам или линиям на странице при перемещении или изменении размера. Это достигается с помощью модуля interact.snap, который поддерживает гибкие параметры для настройки поведения объектов. Snap используется для упрощения выравнивания элементов, создания сеток и визуальных ограничителей при drag-and-drop или resize операциях.

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

interact('.draggable')
  .draggable({
    listeners: {
      move(event) {
        Object.assign(event.target.style, {
          transform: `translate(${event.pageX}px, ${event.pageY}px)`
        });
      }
    },
    modifiers: [
      interact.modifiers.snap({
        targets: [
          interact.snappers.grid({ x: 50, y: 50 })
        ],
        range: Infinity,
        relativePoints: [{ x: 0.5, y: 0.5 }]
      })
    ]
  });

Ключевые моменты:

  • targets — массив целей snap, к которым объект может притягиваться. Это могут быть сетки, конкретные координаты, линии или функции.
  • range — радиус действия snap. Если расстояние до цели превышает это значение, snap не срабатывает.
  • relativePoints — точка на объекте, которая будет использоваться для вычисления положения относительно snap-цели. Например, {x:0.5, y:0.5} — центр элемента.

Привязка к сетке (Grid Snapping)

Привязка к сетке является наиболее распространённым способом использования snap. Она позволяет элементам перемещаться строго по шагам сетки, что особенно удобно для интерфейсов с позиционированием элементов или для визуальных редакторов.

Простейший пример сетки:

interact('.draggable')
  .draggable({
    modifiers: [
      interact.modifiers.snap({
        targets: [
          interact.snappers.grid({ x: 100, y: 100 })
        ],
        range: Infinity,
        relativePoints: [{ x: 0.5, y: 0.5 }]
      })
    ],
    inertia: true
  });
  • x и y определяют шаги сетки по горизонтали и вертикали.
  • Использование inertia: true создаёт эффект естественного движения, при котором объект немного “докатится” до ближайшей позиции сетки после отпускания.

Сетка может быть динамической, если нужно, чтобы шаги зависели от размеров контейнера или других параметров:

const gridSize = () => ({
  x: window.innerWidth / 12,
  y: window.innerHeight / 8
});

interact('.draggable')
  .draggable({
    modifiers: [
      interact.modifiers.snap({
        targets: [
          interact.snappers.grid(gridSize)
        ]
      })
    ]
  });

Snap к произвольным точкам и линиям

Помимо сетки, можно привязывать элементы к произвольным точкам на странице:

interact('.draggable')
  .draggable({
    modifiers: [
      interact.modifiers.snap({
        targets: [
          { x: 200, y: 150 },
          { x: 400, y: 300 },
          interact.snappers.grid({ x: 100, y: 100 })
        ],
        range: 50
      })
    ]
  });
  • В массив targets можно добавлять объекты с координатами или другие snappers.
  • При превышении range объект не притягивается к цели, что позволяет задать локальные ограничения для snap.

Snap также может быть функциональным, с вычислением цели на лету:

interact('.draggable')
  .draggable({
    modifiers: [
      interact.modifiers.snap({
        targets: [
          function (x, y) {
            return { x: Math.round(x / 50) * 50, y: Math.round(y / 50) * 50 };
          }
        ]
      })
    ]
  });

Такой подход полезен для создания адаптивной сетки или сложной логики притяжения к линиям и объектам с нестандартным расположением.


Настройка поведения snap

Параметры snap:

  • offset — смещение snap-цели относительно координат элемента.
  • endOnly — если true, snap срабатывает только после завершения перемещения или ресайза.
  • lockAxis — ограничивает движение объекта по одной оси до срабатывания snap.
  • range — радиус действия, как уже упоминалось, ключевой для контроля чувствительности.
interact('.resizable')
  .resizable({
    edges: { left: true, right: true, bottom: true, top: true },
    modifiers: [
      interact.modifiers.snap({
        targets: [
          interact.snappers.grid({ x: 20, y: 20 })
        ],
        endOnly: true
      })
    ],
    listeners: {
      move(event) {
        let { x, y } = event.target.dataset;
        x = (parseFloat(x) || 0) + event.deltaRect.left;
        y = (parseFloat(y) || 0) + event.deltaRect.top;

        Object.assign(event.target.style, {
          width: `${event.rect.width}px`,
          height: `${event.rect.height}px`,
          transform: `translate(${x}px, ${y}px)`
        });

        event.target.dataset.x = x;
        event.target.dataset.y = y;
      }
    }
  });

endOnly: true особенно полезен для resizable элементов, чтобы snap не мешал плавному изменению размеров, а применялся только после окончания действия.


Комбинация Snap с ограничениями (Restrict)

Snap часто используется вместе с модификатором restrict, который ограничивает область перемещения или изменения размера элемента. Это позволяет создавать интерактивные панели, редакторы и конструктивные интерфейсы:

interact('.draggable')
  .draggable({
    modifiers: [
      interact.modifiers.snap({
        targets: [ interact.snappers.grid({ x: 50, y: 50 }) ],
        range: Infinity
      }),
      interact.modifiers.restrictRect({
        restriction: 'parent',
        endOnly: true
      })
    ]
  });
  • Snap выравнивает элемент по сетке.
  • Restrict предотвращает выход за границы родителя.
  • endOnly позволяет избегать конфликтов между snap и ограничением в реальном времени.

Оптимизация производительности

  • Snap с функциями и динамическими вычислениями лучше использовать аккуратно, так как вычисления выполняются при каждом перемещении.
  • Для сеток и фиксированных точек предпочтительно заранее создавать массив целей.
  • Использование endOnly уменьшает нагрузку на события mousemove или resize.

Применение Snap в интерфейсах

Snap и привязка к сетке находят широкое применение в:

  • Визуальных редакторах (drag-and-drop интерфейсы).
  • Дизайн-системах с сеткой компонентов.
  • Инструментах прототипирования и строительных конструкторах.
  • Игровых интерфейсах с тайловой структурой.

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