Zoom и pan

Для работы с масштабированием (zoom) и панорамированием (pan) в Snap.svg первым делом необходимо создать холст. Создание холста выполняется через конструктор Snap():

var s = Snap("#svgCanvas"); // svgCanvas — id элемента <svg>

После этого можно использовать группы (<g>) для объединения элементов, к которым будет применяться трансформация. Основная идея: трансформации (масштаб, смещение) удобно применять к группе, а не к каждому элементу по отдельности.

var group = s.group();
var rect = s.rect(0, 0, 100, 100).attr({ fill: "#3498db" });
group.add(rect);

Применение масштабирования

Snap.svg использует метод transform() для применения любых преобразований. Для масштабирования используется оператор s (scale). Синтаксис:

element.transform("s<масштаб>[,<cx>,<cy>]");
  • <масштаб> — коэффициент масштабирования (1 — исходный размер, 2 — удвоение и т.д.)
  • <cx>, <cy> — точка центра масштабирования (опционально, по умолчанию (0,0))

Пример масштабирования группы до 150% относительно центра (50,50):

group.transform("s1.5,50,50");

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

var currentTransform = group.transform().localMatrix;
group.transform(currentTransform.scale(1.2, 1.2));

Здесь localMatrix возвращает матрицу текущих трансформаций, и через нее можно последовательно применять масштабирование и смещение, сохраняя предыдущие состояния.

Панорамирование (перемещение)

Для перемещения элементов или групп применяется трансформация t (translate):

element.transform("t<dx>,<dy>");
  • <dx> — смещение по оси X
  • <dy> — смещение по оси Y

Пример сдвига группы на 100px вправо и 50px вниз:

group.transform("t100,50");

Аналогично масштабированию, для последовательного изменения положения следует использовать текущую матрицу трансформации:

var matrix = group.transform().localMatrix;
group.transform(matrix.translate(20, 10));

Комбинированные трансформации

Snap.svg позволяет объединять масштабирование и перемещение в одной строке трансформации:

group.transform("s1.5,50,50 t100,50");

Порядок операций важен: сначала применяется масштаб, затем сдвиг. Если порядок изменить, визуальный результат будет другим.

Управление с помощью событий мыши

Для реализации интерактивного zoom & pan обычно используют события мыши: mousedown, mousemove, mouseup, wheel. Основная схема:

  1. При mousedown сохраняется начальная позиция курсора и текущая матрица трансформации.
  2. При mousemove рассчитывается смещение и применяется translate.
  3. При wheel рассчитывается коэффициент масштабирования и применяется scale относительно позиции курсора.

Пример обработчика масштабирования колесом мыши:

s.node.addEventListener('wheel', function(e) {
    e.preventDefault();
    var scale = e.deltaY < 0 ? 1.1 : 0.9;
    var pt = s.node.createSVGPoint();
    pt.x = e.clientX;
    pt.y = e.clientY;
    var svgP = pt.matrixTransform(s.node.getScreenCTM().inverse());
    var matrix = group.transform().localMatrix;
    matrix.translate(-svgP.x, -svgP.y).scale(scale).translate(svgP.x, svgP.y);
    group.transform(matrix);
});

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

Ограничение масштаба и панорамирования

Для удобства пользователя часто применяют ограничения:

var minScale = 0.5;
var maxScale = 3;

var matrix = group.transform().localMatrix;
var newScale = Math.min(maxScale, Math.max(minScale, matrix.a * scaleFactor));
matrix.a = newScale;
matrix.d = newScale; // одинаковый масштаб по X и Y
group.transform(matrix);

Для панорамирования можно ограничить смещение, проверяя координаты:

var maxX = 500, maxY = 400;
matrix.e = Math.min(maxX, Math.max(-maxX, matrix.e));
matrix.f = Math.min(maxY, Math.max(-maxY, matrix.f));

Анимация zoom и pan

Snap.svg поддерживает плавные анимации через метод animate. Можно анимировать матрицу трансформации:

group.animate({ transform: "s2 t100,50" }, 500, mina.easeinout);

Для более сложных анимаций удобнее использовать матричный подход:

var matrix = new Snap.Matrix();
matrix.scale(2, 2, 50, 50);
matrix.translate(100, 50);
group.animate({ transform: matrix }, 700, mina.elastic);

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

Практическая структура проекта

Для масштабируемого и панорамного интерфейса рекомендуется:

  • Все элементы помещать в одну группу group.
  • Хранить текущую матрицу трансформации отдельно, чтобы легко делать откаты и анимации.
  • Масштабировать относительно курсора, панорамировать через mousedown/mousemove.
  • Ограничивать границы zoom и pan для предотвращения “потери” содержимого.

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