Ограничение перемещения

Snap.svg предоставляет гибкие возможности для управления положением элементов на холсте SVG, включая ограничение их перемещения. Работа с ограничениями особенно полезна при создании интерактивных интерфейсов, игр или визуализаций, где объекты не должны выходить за пределы определённой области.

Использование метода drag()

Метод drag() является основным инструментом для реализации перемещения. Он принимает три функции:

  1. onMove – вызывается при перемещении элемента.
  2. onStart – вызывается при начале перетаскивания.
  3. onEnd – вызывается при завершении перетаскивания.

Пример базового использования:

var s = Snap("#svg");
var circle = s.circle(100, 100, 50).attr({fill: "blue"});

circle.drag(
    function(dx, dy) { // onMove
        this.attr({
            cx: this.data('startX') + dx,
            cy: this.data('startY') + dy
        });
    },
    function() { // onStart
        this.data('startX', this.attr('cx'));
        this.data('startY', this.attr('cy'));
    },
    function() { } // onEnd
);

Ограничение перемещения по границам

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

var width = 500, height = 400;
circle.drag(
    function(dx, dy) {
        var newX = this.data('startX') + dx;
        var newY = this.data('startY') + dy;

        // Ограничение по горизонтали
        if (newX - this.attr('r') < 0) newX = this.attr('r');
        if (newX + this.attr('r') > width) newX = width - this.attr('r');

        // Ограничение по вертикали
        if (newY - this.attr('r') < 0) newY = this.attr('r');
        if (newY + this.attr('r') > height) newY = height - this.attr('r');

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

Ограничение по сетке

Для привязки перемещения к сетке удобно округлять координаты до ближайшего шага. Например, шаг сетки равен 50 пикселям:

var gridSize = 50;
circle.drag(
    function(dx, dy) {
        var newX = Math.round((this.data('startX') + dx) / gridSize) * gridSize;
        var newY = Math.round((this.data('startY') + dy) / gridSize) * gridSize;

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

Ограничение перемещения вдоль осей

Иногда требуется перемещение только по горизонтали или только по вертикали. Для этого достаточно фиксировать одну из координат в функции onMove:

// Перемещение только по X
circle.drag(
    function(dx) {
        this.attr({ cx: this.data('startX') + dx });
    },
    function() { this.data('startX', this.attr('cx')); },
    function() { }
);

// Перемещение только по Y
circle.drag(
    function(_, dy) {
        this.attr({ cy: this.data('startY') + dy });
    },
    function() { this.data('startY', this.attr('cy')); },
    function() { }
);

Комбинирование ограничений

Ограничения можно комбинировать: сетка + границы + ось. В этом случае важно последовательно применять все проверки:

circle.drag(
    function(dx, dy) {
        var newX = Math.round((this.data('startX') + dx) / gridSize) * gridSize;
        var newY = Math.round((this.data('startY') + dy) / gridSize) * gridSize;

        // Ограничение по границам
        newX = Math.max(this.attr('r'), Math.min(width - this.attr('r'), newX));
        newY = Math.max(this.attr('r'), Math.min(height - this.attr('r'), newY));

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

Использование вспомогательных функций

Для упрощения кода и повторного использования ограничений удобно выносить логику в отдельные функции:

function limit(value, min, max) {
    return Math.max(min, Math.min(max, value));
}

function snapToGrid(value, gridSize) {
    return Math.round(value / gridSize) * gridSize;
}

Применение в onMove:

var gridSize = 50;
circle.drag(
    function(dx, dy) {
        var newX = snapToGrid(this.data('startX') + dx, gridSize);
        var newY = snapToGrid(this.data('startY') + dy, gridSize);

        newX = limit(newX, this.attr('r'), width - this.attr('r'));
        newY = limit(newY, this.attr('r'), height - this.attr('r'));

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

Работа с группами элементов

Если нужно перемещать несколько элементов одновременно, лучше объединять их в g-группу и применять ограничения к всей группе:

var group = s.group(circle, s.rect(0,0,30,30));
group.drag(
    function(dx, dy) {
        var newX = limit(this.data('startX') + dx, 0, width);
        var newY = limit(this.data('startY') + dy, 0, height);
        this.transform(`translate(${newX}, ${newY})`);
    },
    function() {
        this.data('startX', 0);
        this.data('startY', 0);
    },
    function() { }
);

Применение ограничений при работе с группами позволяет сохранять относительное расположение элементов внутри группы и предотвращает выход всей группы за границы холста.


Snap.svg предоставляет мощные инструменты для гибкого контроля перемещений. Комбинация метода drag(), функций ограничения координат и вспомогательных методов позволяет создавать сложные интерактивные интерфейсы с точным соблюдением правил перемещения.