В Paper.js анимация объектов по пути реализуется через комбинацию Path, Point, Segment и функций обновления в цикле onFrame. Ключевая идея заключается в том, чтобы вычислять положение объекта на кривой с помощью процентного прогресса или длины пути, а затем обновлять координаты объекта на каждом кадре.
Для начала необходимо создать путь, по которому будет двигаться объект:
var path = new Path();
path.strokeColor = 'black';
path.add(new Point(50, 100));
path.add(new Point(300, 50));
path.add(new Point(450, 200));
path.add(new Point(600, 100));
Каждый Segment пути можно задавать явно через координаты. Path поддерживает линии, кривые Безье и комбинированные формы. Для анимации удобно использовать getPointAt(offset), чтобы получать точку на пути по заданной длине.
Для управления скоростью движения важно знать длину пути:
var totalLength = path.length;
Значение totalLength используется для расчёта позиции
объекта относительно пути в любой момент времени. Paper.js автоматически
вычисляет длину сегментов кривых и прямых линий.
Создаём объект, который будет двигаться:
var circle = new Path.Circle({
center: path.firstSegment.point,
radius: 10,
fillColor: 'red'
});
Для анимации используется событие onFrame, которое вызывается при каждом кадре рендеринга:
var progress = 0; // Текущий прогресс в пикселях
var speed = 2; // Скорость движения
function onFrame(event) {
progress += speed;
if (progress > totalLength) {
progress = 0; // Циклическое движение
}
circle.position = path.getPointAt(progress);
}
Метод getPointAt(offset) возвращает координаты точки на пути на указанном расстоянии от начала. Таким образом можно легко контролировать скорость и направление движения.
Если путь содержит кривые Безье, getPointAt корректно интерполирует положение по кривой, обеспечивая плавное движение. Например:
var bezierPath = new Path();
bezierPath.strokeColor = 'blue';
bezierPath.add(new Point(100, 300));
bezierPath.cubicCurveTo(new Point(200, 100), new Point(400, 500), new Point(500, 300));
var dot = new Path.Circle({
center: bezierPath.firstSegment.point,
radius: 8,
fillColor: 'green'
});
var t = 0;
function onFrame(event) {
t += 0.01;
if (t > 1) t = 0;
dot.position = bezierPath.getPointAt(t * bezierPath.length);
}
Использование коэффициента t от 0 до 1 позволяет
интерполировать движение по всей длине пути, независимо от формы
кривой.
Для естественной анимации полезно вращать объект так, чтобы он был ориентирован вдоль траектории:
function onFrame(event) {
progress += speed;
if (progress > totalLength) progress = 0;
var point = path.getPointAt(progress);
var tangent = path.getTangentAt(progress); // Вектор касательной
circle.position = point;
circle.rotation = tangent.angle; // Угол направления движения
}
getTangentAt(offset) возвращает касательный вектор, который указывает направление движения. Это особенно важно для объектов с выраженной ориентацией, например, машин или стрелок.
Для более сложных эффектов можно использовать функции easing. Пример ускорения и замедления:
function easeInOut(t) {
return t < 0.5
? 2 * t * t
: -1 + (4 - 2 * t) * t;
}
var t = 0;
function onFrame(event) {
t += 0.005;
if (t > 1) t = 0;
var easedT = easeInOut(t);
circle.position = path.getPointAt(easedT * path.length);
}
Использование easedT позволяет плавно изменять скорость
движения, создавая эффект ускорения в начале и замедления в конце
пути.
Для создания нескольких объектов на одном пути можно использовать массив прогрессов:
var balls = [];
for (var i = 0; i < 5; i++) {
balls.push(new Path.Circle({
center: path.firstSegment.point,
radius: 6,
fillColor: 'orange'
}));
}
var progresses = [0, 50, 100, 150, 200];
function onFrame(event) {
for (var i = 0; i < balls.length; i++) {
progresses[i] += speed;
if (progresses[i] > totalLength) progresses[i] -= totalLength;
balls[i].position = path.getPointAt(progresses[i]);
}
}
Так можно легко реализовать эффект цепочки объектов, движущихся по одной траектории с равными интервалами.
Помимо визуального перемещения, Paper.js позволяет определять процент пройденного пути, вычислять расстояние до конца или до других сегментов. Это открывает возможности для взаимодействия объектов:
var distanceToEnd = path.length - progress;
if (distanceToEnd < 10) {
circle.fillColor = 'purple';
}
Использование длины пути и касательных в сочетании с событиями onFrame позволяет строить сложные анимации с динамическими эффектами, столкновениями и синхронизацией нескольких объектов.