Встроенная поддержка перетаскивания

В библиотеке Snap.svg перетаскивание объектов реализуется с помощью метода drag(), который применяется к любому элементу SVG, созданному через Snap. Основной синтаксис метода:

element.drag(onmove, onstart, onend, [mcontext], [scontext], [econtext]);
  • onmove — функция, вызываемая при каждом движении курсора во время перетаскивания. Обычно в ней обновляется позиция элемента.
  • onstart — функция, вызываемая при начале перетаскивания. Часто используется для сохранения начальных координат.
  • onend — функция, вызываемая при завершении перетаскивания.
  • mcontext, scontext, econtext — контексты для передачи дополнительных данных в соответствующие функции (необязательные параметры).

Пример простого перетаскивания прямоугольника:

var s = Snap(800, 600);
var rect = s.rect(100, 100, 150, 100).attr({fill: "#3498db"});

var startX, startY;

rect.drag(
    function(dx, dy) { // onmove
        this.attr({
            x: startX + dx,
            y: startY + dy
        });
    },
    function() { // onstart
        startX = this.attr("x");
        startY = this.attr("y");
    },
    function() { // onend
        console.log("Перетаскивание завершено");
    }
);

В этом примере dx и dy — смещения относительно начальной позиции, вычисляемые автоматически библиотекой.


Тонкости работы с координатами

Snap.svg хранит координаты в атрибутах x и y для прямоугольников, cx и cy для кругов и transform для сложных фигур. Для универсальной обработки перетаскивания удобно использовать метод transform():

element.transform(`translate(${dx}, ${dy})`);

Однако при использовании transform() необходимо учитывать предыдущие трансформации. Для корректного суммирования смещений применяется метод matrix():

var matrix = element.transform().localMatrix;
matrix.e += dx;
matrix.f += dy;
element.transform(matrix);

Такой подход обеспечивает стабильное поведение элементов даже при наличии поворотов или масштабирования.


Ограничение области перетаскивания

Часто требуется ограничить движение элемента рамками SVG или другими координатами. Это делается внутри функции onmove с проверкой смещений:

rect.drag(
    function(dx, dy) {
        var newX = Math.max(0, Math.min(startX + dx, 650)); // 650 = ширина SVG - ширина прямоугольника
        var newY = Math.max(0, Math.min(startY + dy, 500)); // 500 = высота SVG - высота прямоугольника
        this.attr({x: newX, y: newY});
    },
    function() {
        startX = this.attr("x");
        startY = this.attr("y");
    }
);

Использование Math.max и Math.min предотвращает выход элементов за границы.


Перетаскивание сложных групп элементов

Snap.svg позволяет объединять элементы в группы через g() и задавать перетаскивание сразу всей группы:

var circle = s.circle(50, 50, 40).attr({fill: "#e74c3c"});
var text = s.text(50, 55, "Привет").attr({fill: "#fff", "text-anchor": "middle"});
var group = s.group(circle, text);

group.drag(
    function(dx, dy) {
        this.transform(`translate(${dx}, ${dy})`);
    },
    function() {},
    function() {}
);

При этом dx и dy применяются ко всей группе, что позволяет легко создавать интерактивные комбинации объектов.


События при перетаскивании

Помимо функций onstart, onmove и onend, можно использовать стандартные события DOM для расширенного контроля:

  • element.mousedown() — запуск кастомного поведения при нажатии.
  • element.mouseup() — завершение действия.
  • element.mousemove() — отслеживание движения мыши без привязки к drag.

Комбинация этих событий с методами Snap.svg позволяет реализовать сложные механики, например, динамическое изменение размера или поворот объектов во время перетаскивания.


Сочетание с анимацией

Snap.svg позволяет комбинировать перетаскивание с анимацией через метод animate():

rect.drag(
    function(dx, dy) {
        this.attr({x: startX + dx, y: startY + dy});
    },
    function() {
        startX = this.attr("x");
        startY = this.attr("y");
    },
    function() {
        this.animate({fill: "#2ecc71"}, 300);
    }
);

Такой подход позволяет визуально отмечать завершение перетаскивания или изменение состояния элемента.


Перетаскивание с сохранением исходного положения

Иногда требуется, чтобы после отпускания элемент возвращался на исходную позицию. Это делается в onend с помощью метода animate:

rect.drag(
    function(dx, dy) {
        this.attr({x: startX + dx, y: startY + dy});
    },
    function() {
        startX = this.attr("x");
        startY = this.attr("y");
    },
    function() {
        this.animate({x: startX, y: startY}, 500, mina.easeinout);
    }
);

Метод mina.easeinout обеспечивает плавное движение назад, создавая эффект “упругости”.


Перетаскивание с дополнительными ограничениями

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

rect.drag(
    function(dx, dy) {
        var newX = startX + dx;
        var newY = startY + dy;
        if (!checkCollision(newX, newY, rect)) {
            this.attr({x: newX, y: newY});
        }
    },
    function() {
        startX = this.attr("x");
        startY = this.attr("y");
    }
);

function checkCollision(x, y, elem) {
    var other = s.select("#staticRect");
    return x < other.attr("x") + other.attr("width") &&
           x + elem.attr("width") > other.attr("x") &&
           y < other.attr("y") + other.attr("height") &&
           y + elem.attr("height") > other.attr("y");
}

Такой подход позволяет создавать интерактивные игры и редакторы внутри SVG, полностью управляя поведением элементов.