Для этого сделаем или позаимствуем 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\b([\s\S]*?)><\/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);
Код упрощенный, полный код здесь:
Комментарии (15)
Keyten
05.12.2018 17:52У меня пример очень быстро начинает тормозить, хотя ничего тормозящего там быть по идее не должно. Что-то там очень неоптимизировано.
Ну и перерисовывать лучше
а) через requestAnimationFrame, а не таймаут
б) только когда что-то действительно изменилось на канвасеcitizen55 Автор
05.12.2018 18:44Если вы про второй, там полно вещей которые нагружают процессор, и код я бы сказал очень не оптимизированный, да и цели такой не было, все таки это пример, а не готовое решение. А насчет таймаута, вы наверное не дошли до места, где вызывается rAF.
Keyten
06.12.2018 18:43А, тогда ок :) Про raf я просто не так понял кусок кода, да, вы правы.
Если хотите, покажу, куда копать, чтобы освоить ещё пару интересных вещей (это к комментарию habr.com/post/432114/#comment_19458236).citizen55 Автор
07.12.2018 15:31Интригу создали, если они и правда интересные, то не скрывайте, напишите обязательно.
vmm86
05.12.2018 23:07В 2013 году сделали простенький сайт для детского сада. Пришла в голову идея в заголовок поместить движущийся паровозик из мультфильма, т.к. сад подведомствен «РЖД». Реализовали это встроенными средствами самого SVG, в частности, используя теги AnimateTransform и AnimateMotion. Кажется, получилось неплохо.-)
Sergey-Pimenov
05.12.2018 23:57SVG SMIL вообще крутейшая вещь. Одно время команда Хромиума объявила её «Deprecated» и собиралась вообще выпилить её, так SMIL и прожил в таком статусе несколько лет. Прочитал ваше сообщение, решил проверить на caniuse как там поживает статус SMIL'а. Оказалось ещё в середине 2016 хромиумцы под давлением сообщества решили отменить «депрекацию» и выпиливание. Это же просто праздник, теперь можно смело продолжать использовать на проде! Просто нативных аналогов вообще нет, там и морфинг, и (как в вашем примере) анимация вдоль пути. В общем хорошо, что вы упомянули ваш опыт
citizen55 Автор
06.12.2018 10:45Получилось симпатично, даже колеса крутятся, но у вас анимация «внутри» svg, в моем случае изначально надо было двигать растровое изображение на холсте. Хотя, таким образом можно перемещать и любой блок на html странице, но говорить о широкой поддержке, в этом случае, вряд ли приходится.
Sergey-Pimenov
Очень-очень интересная техника, спасибо что написали. getPointAtLength — вообще открытие, это просто огонь! Если у вас ещё какие-нибудь новые интересные приёмы найдутся, напишите, пожалуйста. А то редко что-то новое попадается в сфере анимации, а тут ещё и Vanilla JS вариант применения.
citizen55 Автор
Спасибо за положительный комментарий. Если освою что нибудь интересное, обязательно поделюсь.
noodles
так Deprecated же(
Sergey-Pimenov
Я уж было испугался, но этот метод просто переехал в другое место. Так что можно просто использовать новый вариант с фолбеком на старый вариант (который в статье)
citizen55 Автор
Да как бы ссылка была на новый интерфейс — SVGGeometryElement. Старый был SVGPathElement
3aicheg
Недавно для мелкого хобби-проекта пытался сделать анимированную пунктирную линию поверх произвольного фона, и чтобы каждый пунктир был закруглён по концам и ещё обведён контрастным цветом. Ну, типа, юзер потыкал мышью по картинке, а потом через все точки пунктирный сплайн медленно прорисовался, такой юзкейс. Сделал через анимированную маску видимости, всё получилось, кроме самопересечений. Их пришлось делать, пряча svg, кладя вместо него канву и рисуя по ней тот же путь, получая точки через getPointAtLength.