Использование requestAnimationFrame

В библиотеке Snap.svg анимация элементов SVG может быть реализована как через встроенные методы Snap, так и с использованием нативного API браузера — requestAnimationFrame. Этот подход обеспечивает более плавную анимацию и экономию ресурсов, так как браузер синхронизирует обновления с частотой перерисовки экрана.

requestAnimationFrame принимает функцию обратного вызова, которая вызывается перед следующим перерисовыванием страницы. Использование его с Snap.svg позволяет напрямую управлять свойствами элементов и анимационными шагами.

var s = Snap(400, 400);
var circle = s.circle(50, 50, 40).attr({fill: "#3498db"});

function animateCircle(timestamp) {
    var speed = 0.1; // пикселей за миллисекунду
    circle.attr({cx: circle.attr("cx") + speed});
    if (circle.attr("cx") < 400) {
        requestAnimationFrame(animateCircle);
    }
}

requestAnimationFrame(animateCircle);

В этом примере круг плавно перемещается по оси X. Использование requestAnimationFrame вместо setInterval или setTimeout гарантирует, что анимация будет максимально синхронизирована с обновлением экрана.


Управление анимационными параметрами

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

var start = null;
var circle = s.circle(50, 200, 30).attr({fill: "#e74c3c"});

function animate(timestamp) {
    if (!start) start = timestamp;
    var progress = timestamp - start;
    var newX = 50 + progress * 0.2; // скорость 0.2 пикселя на миллисекунду
    circle.attr({cx: newX});
    if (newX < 400) {
        requestAnimationFrame(animate);
    }
}

requestAnimationFrame(animate);

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


Комбинирование Snap.svg и requestAnimationFrame

Snap.svg предоставляет метод animate, но иногда необходимо реализовать сложные анимации с кастомной логикой, например движение по кривой Безье или анимацию нескольких свойств одновременно с разными скоростями. В таких случаях удобно использовать requestAnimationFrame:

var path = s.path("M10,80 C40,10 65,10 95,80").attr({stroke: "#2ecc71", fill: "none"});
var circle = s.circle(10, 80, 5).attr({fill: "#2ecc71"});
var start = null;

function animateAlongPath(timestamp) {
    if (!start) start = timestamp;
    var progress = (timestamp - start) / 2000; // 2 секунды на полное движение
    if (progress > 1) progress = 1;
    
    var point = Snap.path.getPointAtLength(path, progress * path.getTotalLength());
    circle.attr({cx: point.x, cy: point.y});
    
    if (progress < 1) {
        requestAnimationFrame(animateAlongPath);
    }
}

requestAnimationFrame(animateAlongPath);

Здесь Snap.path.getPointAtLength используется для вычисления координат по длине пути. Такой подход позволяет создавать точные и сложные траектории движения.


Оптимизация анимации

  1. Избегать лишних вычислений внутри колбэка – значения, которые не изменяются каждый кадр, стоит вычислять заранее.
  2. Группировать обновления атрибутов – вместо многократного вызова attr для разных свойств, лучше передавать объект с несколькими ключами.
  3. Использовать временные переменные – например, хранить длину пути в переменной, чтобы не вызывать getTotalLength() каждый кадр.
var pathLength = path.getTotalLength();

function optimizedAnimate(timestamp) {
    if (!start) start = timestamp;
    var progress = (timestamp - start) / 2000;
    if (progress > 1) progress = 1;

    var point = Snap.path.getPointAtLength(path, progress * pathLength);
    circle.attr({cx: point.x, cy: point.y});

    if (progress < 1) requestAnimationFrame(optimizedAnimate);
}

requestAnimationFrame(optimizedAnimate);

Управление несколькими анимациями

Для синхронного движения нескольких объектов удобно использовать одну функцию requestAnimationFrame, которая обновляет все элементы сразу. Это снижает нагрузку на рендеринг и упрощает управление временем:

var circle1 = s.circle(50, 50, 20).attr({fill: "#f1c40f"});
var circle2 = s.circle(50, 100, 20).attr({fill: "#9b59b6"});
var start = null;

function animateAll(timestamp) {
    if (!start) start = timestamp;
    var progress = (timestamp - start) / 2000;
    if (progress > 1) progress = 1;

    circle1.attr({cx: 50 + 200 * progress});
    circle2.attr({cx: 50 + 100 * progress});

    if (progress < 1) requestAnimationFrame(animateAll);
}

requestAnimationFrame(animateAll);

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


Заключение к практике использования

Использование requestAnimationFrame с Snap.svg открывает возможности для создания плавных, оптимизированных анимаций с точным контролем времени и траекторий. Оно позволяет реализовать сложные сценарии, которые трудно или невозможно воспроизвести только через метод animate Snap.svg. Управление временными метками, предварительные вычисления и групповое обновление атрибутов делают анимации более производительными и предсказуемыми.