[Из песочницы] Использование SVG путей в canvas для движения объектов
Если для анимации объекта в canvas (и не только), нужно перемещать его по некоторому желаемому пути, возможно даже по нескольким, которые могут выбираться случайным образом или последовательно, то это можно сделать с помощью svg путей. Давайте, для начала, запустим по траектории простой, но зеленый квадрат.
Для этого сделаем или позаимствуем svg, с одним или несколькими путями.
Создадим элемент с помощью функции document.createElementNS. MDN сообщает нам, что метод имеет базовую поддержку во всех современных браузерах. Затем добавим созданному элементу путь.
let path = document.createElementNS("http://www.w3.org/2000/svg", "path");
path.setAttribute('d', 'M148.185,118.975c0,0-92.592,39.507-80.247,79.013,s79.012,143.21,129.629,124.691s64.198-113.856,120.988-100.755s118.518,30.384,116.049,109.397s-82.715,118.519-97.53,201.235,s-92.593,139.505,0,159.259');
Здесь, в атрибуты, внесен первый попавшийся на глаза путь из какого-то svg файла, методом копируй-вставляй. Конечно это не единственный и более того, не самый удобный способ, но достаточно наглядный для использование в первом примере.
Теперь в цикле, будем получать координаты точек пути и назначать их нашему объекту. Для этого нам хватит двух методов SVGGeometryElement:
path.getTotalLength()
возвращает вычисленное значение общей длины пути и
path.getPointAtLength(index)
Получает аргументом float число, а возвращает объект SVGPoint у которого есть, интересующие нас, координаты x и y. При значениях аргумента, меньше нуля или больше длины пути, в качестве результата будет возвращаться первая или последняя точки соответственно.
При обновлении кадра, получим точку и используем ее координаты для движения.
→ Полный код примера на codepen
Но, можно использовать более интересный вариант двигать объект по координатам нескольких путей, например такой:
Опять же, возьмем svg файл с несколькими путями. Тот который был использован в примере, сделан в редакторе Inscape. Теперь надо получить эти пути, это возможно через разбор объекта или, если svg был получен в виде текстового файла, то следующей функцией, с помощью регулярных выражений, можно получить их как строки.
extractPathsfromSvg: function(svg){
let results = svg.match(/<\/path>/g);
let paths = [];
let len = results.length;
for(let i = 0; i < len; i++){
let str = results[i];
let data = str.match(/[^\w]d="([\s\S]*?)"/);
paths.push(data[1]);
}
return paths;
}
После создания массива путей, остается извлекать их по очереди и обрабатывать, таким же образом, как и единственный путь для движения квадрата. получая интересные эффекты.
Полный код примера смотрите ниже.
Что бы добавить больше контроля при движение объекта по координатам пути можно использовать твины. Для тестовых примеров я взял первую попавшуюся на глаза библиотеку GreenSock, но это могла оказаться и любая другая.
В первом случае при движении квадрата по единственному пути, создадим промежуточный объект помощник, и передадим его при создании твина.
var helper = {progress: 0}
helper.update = function(value){
point = path.getPointAtLength(totalLength * helper.progress);
x = point.x;
y = point.y;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, x, y );
}
var tw = new TweenLite.to(helper, 5, {progress: 0, });
tw.eventCallback("onUpdate", helper.update);
Увидеть движение квадрата по пути с использованием твина, в первом примере на codepen, можно поставив галочку use tween.
При движении по нескольким путям, поступим следующим образом. Как и ранее создадим объект helper, со свойством progress. Посчитаем общую длину всех путей, и назначим ее handler.progress. Создадим переменную traversed в которой будут суммироваться уже пройденные пути.
Для получения точки на текущем пути, отнимаем от helper.progress, который меняется в твине, уже пройденный путь — traversed. Используем координаты точки как обычно.
var traversed = 0;
helper.progress = totalLenghtAllPath;
helper.update = function() {
var localPoint = helper.progress - traversed;
if(localPoint > curPath.getTotalLength()){
traversed += curPath.getTotalLength();
curPath = paths[next()];
if(curPath){
return false;
}
localPoint = helper.progress - traversed;
}
/* код которому нужны координаты точки пути */
}
var tw = TweenLite.to(
helper,
25,
{progress: totalLenghtAllPath, ease: Power2.easeOut }
);
tw.eventCallback("onUpdate", helper.update);
Код упрощенный, полный код здесь: