Drag and drop

Snap.svg предоставляет удобные методы для реализации перетаскивания элементов SVG с помощью мыши или тач-событий. Ключевой механизм основан на методе .drag(), который позволяет назначить функции для трёх событий: onMove, onStart, onEnd.

element.drag(onMove, onStart, onEnd);
  • onMove(dx, dy, x, y, event) – вызывается при перемещении элемента.

    • dx и dy – смещение по осям X и Y относительно позиции начала перетаскивания.
    • x и y – текущие координаты курсора относительно SVG.
    • event – объект события браузера.
  • onStart(x, y, event) – вызывается при начале перетаскивания. Позволяет сохранять исходные координаты элемента или устанавливать состояние.

  • onEnd(event) – вызывается при завершении перетаскивания, можно реализовать логику «отпускания» или проверки позиции.

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

var s = Snap("#svg");
var circle = s.circle(100, 100, 50).attr({ fill: "#3498db", stroke: "#2980b9", strokeWidth: 3 });

circle.drag(
    function(dx, dy) {
        this.attr({ cx: this.data('ox') + dx, cy: this.data('oy') + dy });
    },
    function() {
        this.data('ox', this.attr('cx'));
        this.data('oy', this.attr('cy'));
    },
    function() {
        console.log('Перетаскивание завершено');
    }
);

Здесь data('ox') и data('oy') используются для хранения исходных координат элемента, что позволяет правильно вычислять смещение при движении.

Перетаскивание с ограничениями

Для создания ограничений движения используется проверка координат внутри onMove. Например, чтобы элемент оставался внутри SVG-контейнера:

var width = s.node.clientWidth;
var height = s.node.clientHeight;

circle.drag(
    function(dx, dy) {
        var newX = this.data('ox') + dx;
        var newY = this.data('oy') + dy;

        newX = Math.max(50, Math.min(width - 50, newX));
        newY = Math.max(50, Math.min(height - 50, newY));

        this.attr({ cx: newX, cy: newY });
    },
    function() {
        this.data('ox', this.attr('cx'));
        this.data('oy', this.attr('cy'));
    },
    function() {}
);

Здесь используется метод Math.max и Math.min для ограничения координат, учитывая радиус круга.

Drag для сложных элементов

Для групп элементов (<g>), состоящих из нескольких фигур, также можно использовать .drag(). В этом случае полезно применять трансформации через transform():

var group = s.group(
    s.rect(0, 0, 100, 50).attr({ fill: "#e74c3c" }),
    s.text(10, 30, "Перетащи меня").attr({ fill: "#fff", fontSize: 16 })
);

group.drag(
    function(dx, dy) {
        this.transform(this.data('origTransform') + "t" + dx + "," + dy);
    },
    function() {
        this.data('origTransform', this.transform().local);
    },
    function() {}
);

Использование трансформации t(dx, dy) позволяет перемещать всю группу без изменения координат отдельных элементов.

Интерактивные эффекты при перетаскивании

Snap.svg поддерживает плавное изменение атрибутов во время drag:

  • Изменение цвета при начале перетаскивания:
circle.drag(
    function(dx, dy) { this.attr({ cx: this.data('ox') + dx, cy: this.data('oy') + dy }); },
    function() { this.data('ox', this.attr('cx')); this.data('oy', this.attr('cy')); this.attr({ fill: "#2ecc71" }); },
    function() { this.attr({ fill: "#3498db" }); }
);
  • Реализация «магнита» к определённым точкам:
var points = [{x:200,y:200},{x:400,y:100}];

circle.drag(
    function(dx, dy) {
        var newX = this.data('ox') + dx;
        var newY = this.data('oy') + dy;
        this.attr({ cx: newX, cy: newY });
    },
    function() {
        this.data('ox', this.attr('cx'));
        this.data('oy', this.attr('cy'));
    },
    function() {
        var closest = points.reduce((a, b) => {
            var da = Math.hypot(a.x - this.attr('cx'), a.y - this.attr('cy'));
            var db = Math.hypot(b.x - this.attr('cx'), b.y - this.attr('cy'));
            return da < db ? a : b;
        });
        this.attr({ cx: closest.x, cy: closest.y });
    }
);

Эта техника полезна для создания snap-to-grid интерфейсов и игр с перетаскиванием объектов.

Touch и мультитач

Snap.svg корректно работает с тач-событиями на мобильных устройствах. Метод .drag() автоматически поддерживает touchstart, touchmove и touchend. Для мультитач можно расширить логику с использованием event.touches и вычислять среднюю позицию пальцев для перемещения группы элементов.

Производительность при большом количестве элементов

  • Для оптимизации рекомендуется использовать group и трансформации вместо изменения атрибутов координат каждого элемента отдельно.
  • Не стоит добавлять сложные вычисления внутри onMove; лучше хранить промежуточные данные в data() объекта.
  • Для плавной анимации и уменьшения лагов использовать requestAnimationFrame при необходимости интеграции drag с анимациями.

Закрепление координат и возврат

Snap.svg позволяет реализовать возврат элемента на исходную позицию через анимацию в onEnd:

circle.drag(
    function(dx, dy) { this.attr({ cx: this.data('ox') + dx, cy: this.data('oy') + dy }); },
    function() { this.data('ox', this.attr('cx')); this.data('oy', this.attr('cy')); },
    function() {
        this.animate({ cx: this.data('ox'), cy: this.data('oy') }, 300, mina.easeinout);
    }
);

Использование mina.easeinout создаёт плавный эффект возврата, повышая интерактивность интерфейса.

Итоговые рекомендации

  • Всегда сохранять исходные координаты в data().
  • Использовать трансформации для групп объектов.
  • Ограничивать перемещение через Math.min/Math.max или логические проверки.
  • Добавлять визуальные эффекты в onStart и onEnd для улучшения UX.
  • Для сложных интерфейсов использовать оптимизацию с requestAnimationFrame и минимизировать вычисления в onMove.

Snap.svg делает drag and drop интуитивным и гибким, позволяя создавать интерактивные диаграммы, игры и визуальные редакторы на основе SVG.