Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can you "bend" a SVG around bezier path while animating?

Is there any way to "bend" an SVG object as its being animated along a bezier path? Ive been using mostly GSAP for animating things. The effect would look something like this: https://www.behance.net/gallery/49401667/Twisted-letters-2 (the one with the blue pencil). I have managed to get the red arrow to animate along the path but the shape stays the same the whole time. Id like for it to follow along the green path and bend as it goes around the curve so that at the end of the animation it has the shape of the purple arrow. Here is the codepen.

GSAP code:

var motionPath = MorphSVGPlugin.pathDataToBezier("#motionPath", {align:"#arrow1"});
var tl1 = new TimelineMax({paused:true, reversed:true});
tl1.set("#arrow1", {xPercent:-50, yPercent:-50});
tl1.to("#arrow1", 4, {bezier:{values:motionPath, type:"cubic"}});


$("#createAnimation").click(function(){
    tl1.reversed() ? tl1.play() : tl1.reverse();
});

Is there a way to do this with just GSAP? Or will I need something like Pixi?

like image 809
comp1201 Avatar asked Jan 11 '19 23:01

comp1201


1 Answers

This is how I would do it:

First I need an array of points to draw the arrow and a track. I want to move the arrow on the track, and the arrow should bend following the track. In order to achieve this effect with every frame of the animation I'm calculating the new position of the points for the arrow.

Also: the track is twice as long as it seams to be.

Please read the comments in the code

let track = document.getElementById("track");
let trackLength = track.getTotalLength();
let t = 0.1;// the position on the path. Can take values from 0 to .5
// an array of points used to draw the arrow
let points = [
[0, 0],[6.207, -2.447],[10.84, -4.997],[16.076, -7.878],[20.023, -10.05],[21.096, -4.809],[25.681, -4.468],[31.033, -4.069],[36.068, -3.695],[40.81, -3.343],[45.971, -2.96],[51.04, -2.584],[56.075, -2.21],[60.838, -1.856],[65.715, -1.49],[71.077, -1.095],[75.956, -0.733],[80, 0],[75.956, 0.733],[71.077, 1.095],[65.715, 1.49],[60.838, 1.856],[56.075, 2.21],[51.04, 2.584],[45.971, 2.96],[40.81, 3.343],[36.068, 3.695],[31.033, 4.069],[25.681, 4.468],[21.096, 4.809],[20.023, 10.05],[16.076, 7.878],[10.84, 4.997],[6.207, 2.447],[0, 0]
];

function move() {
  requestAnimationFrame(move);
  if (t > 0) {
    t -= 0.001;
  } else {
    t = 0.5;
  }
  let ry = newPoints(track, t);
  drawArrow(ry);
}

move();



function newPoints(track, t) {
  // a function to change the value of every point on the points array
  let ry = [];
  points.map(p => {
    ry.push(getPos(track, t, p[0], p[1]));
  });
  return ry;
}

function getPos(track, t, d, r) {
  // a function to get the position of every point of the arrow on the track
  let distance = d + trackLength * t;
  // a point on the track
  let p = track.getPointAtLength(distance);
  // a point near p used to calculate the angle of rotation
  let _p = track.getPointAtLength((distance + 1) % trackLength);
  // the angle of rotation on the path
  let a = Math.atan2(p.y - _p.y, p.x - _p.x) + Math.PI / 2;
  // returns an array of coordinates: the first is the x, the second is the y
  return [p.x + r * Math.cos(a), p.y + r * Math.sin(a)];
}

function drawArrow(points) {
  // a function to draw the arrow in base of the points array
  let d = `M${points[0][0]},${points[0][1]}L`;
  points.shift();
  points.map(p => {
    d += `${p[0]}, ${p[1]} `;
  });
  d += "Z";
  arrow.setAttributeNS(null, "d", d);
}
svg {
  display: block;
  margin: 2em auto;
  border: 1px solid;
  overflow: visible;
  width:140vh;
}
#track {
  stroke: #d9d9d9;
  vector-effect: non-scaling-stroke;
}
<svg viewBox="-20 -10 440 180">

<path id="track" fill="none"
d="M200,80 
   C-50,280 -50,-120 200,80
   C450,280 450,-120 200,80
   C-50,280 -50,-120 200,80
   C450,280 450,-120 200,80Z" />
   
<path id="arrow" d="" />  
</svg>
like image 167
enxaneta Avatar answered Oct 23 '22 22:10

enxaneta