JavaScript
Canvas
December 2018 5

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

From Sandbox
Если для анимации объекта в 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); 

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


+14
3.3k 50
Comments 16
Top of the day