Использование SVG путей в canvas для движения объектов

Если для анимации объекта в canvas (и не только), нужно перемещать его по некоторому желаемому пути, возможно даже по нескольким, которые могут выбираться случайным образом или последовательно, то это можно сделать с помощью svg путей. Давайте, для начала, запустим по траектории простой но зеленый квадрат.

image


Для этого сделаем или позаимствуем 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

Но, можно использовать более интересный вариант двигать объект по координатам нескольких путей, например такой:

image


Опять же, возьмем 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); 

Код упрощенный, полный код здесь:

Поделиться публикацией

Похожие публикации

Комментарии 16
    +2
    Очень-очень интересная техника, спасибо что написали. getPointAtLength — вообще открытие, это просто огонь! Если у вас ещё какие-нибудь новые интересные приёмы найдутся, напишите, пожалуйста. А то редко что-то новое попадается в сфере анимации, а тут ещё и Vanilla JS вариант применения.
      0
      Спасибо за положительный комментарий. Если освою что нибудь интересное, обязательно поделюсь.
        0
        getPointAtLength — вообще открытие, это просто огонь!

        так Deprecated же(
          0

          Я уж было испугался, но этот метод просто переехал в другое место. Так что можно просто использовать новый вариант с фолбеком на старый вариант (который в статье)

            0
            Да как бы ссылка была на новый интерфейс — SVGGeometryElement. Старый был SVGPathElement
          0
          Недавно для мелкого хобби-проекта пытался сделать анимированную пунктирную линию поверх произвольного фона, и чтобы каждый пунктир был закруглён по концам и ещё обведён контрастным цветом. Ну, типа, юзер потыкал мышью по картинке, а потом через все точки пунктирный сплайн медленно прорисовался, такой юзкейс. Сделал через анимированную маску видимости, всё получилось, кроме самопересечений. Их пришлось делать, пряча svg, кладя вместо него канву и рисуя по ней тот же путь, получая точки через getPointAtLength.
          0
          У меня пример очень быстро начинает тормозить, хотя ничего тормозящего там быть по идее не должно. Что-то там очень неоптимизировано.
          Ну и перерисовывать лучше
          а) через requestAnimationFrame, а не таймаут
          б) только когда что-то действительно изменилось на канвасе
            0
            Если вы про второй, там полно вещей которые нагружают процессор, и код я бы сказал очень не оптимизированный, да и цели такой не было, все таки это пример, а не готовое решение. А насчет таймаута, вы наверное не дошли до места, где вызывается rAF.
              0
              А, тогда ок :) Про raf я просто не так понял кусок кода, да, вы правы.

              Если хотите, покажу, куда копать, чтобы освоить ещё пару интересных вещей (это к комментарию habr.com/post/432114/#comment_19458236).
                0
                Интригу создали, если они и правда интересные, то не скрывайте, напишите обязательно.
                  0
                  Написал в личку, ответите?
            0
            В 2013 году сделали простенький сайт для детского сада. Пришла в голову идея в заголовок поместить движущийся паровозик из мультфильма, т.к. сад подведомствен «РЖД». Реализовали это встроенными средствами самого SVG, в частности, используя теги AnimateTransform и AnimateMotion. Кажется, получилось неплохо.-)
              +1
              SVG SMIL вообще крутейшая вещь. Одно время команда Хромиума объявила её «Deprecated» и собиралась вообще выпилить её, так SMIL и прожил в таком статусе несколько лет. Прочитал ваше сообщение, решил проверить на caniuse как там поживает статус SMIL'а. Оказалось ещё в середине 2016 хромиумцы под давлением сообщества решили отменить «депрекацию» и выпиливание. Это же просто праздник, теперь можно смело продолжать использовать на проде! Просто нативных аналогов вообще нет, там и морфинг, и (как в вашем примере) анимация вдоль пути. В общем хорошо, что вы упомянули ваш опыт
                0
                Получилось симпатично, даже колеса крутятся, но у вас анимация «внутри» svg, в моем случае изначально надо было двигать растровое изображение на холсте. Хотя, таким образом можно перемещать и любой блок на html странице, но говорить о широкой поддержке, в этом случае, вряд ли приходится.
                  0

                  Я прочитав заголовок подумал что canvas в svg запихнули и двигали по путям.

                    0
                    Шикарно было бы наоборот — svg в canvas запихать.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое