Animate Anything Along an SVG Path

SVG is a very neat format to display any illustration, icon or logo on a website. Furthermore, they can be animated in CSS or JavaScript to make them more attractive. But SVG can also be used for their data only, without the visual! Let me explain…

An SVG is a vector image format, which means it is not made of coloured pixels, but math functions that, once interpreted, can be rendered on screen. Since the browser must convert the file from functions to actual pixels, it also let us access a wide variety of methods to either manipulate, or retrieve data from the math.

In today’s article, we will explore the function getPointAtLength() and see how we can use the data of an SVG path for creative use cases such as the demo below.

See the Pen Random dots along path – CodePen Challenge by Louis Hoebregts (@Mamboleoo) on CodePen.

âš  If you are not familiar with SVG, this CSS-Tricks article is a good start!

The method getPointAtLength()

If we take a look at the MDN documentation page about the method it says:
The SVGGeometryElement.getPointAtLength() method returns the point at a given distance along the path.

The method will give us the coordinates of a point that is precisely along the path at a specific distance that we send as a parameter.

For example path.getPointAtLength(10) will return an SVGPoint (an object) with x & y coordinates.

Since we need to give the distance of our point, it means we will most likely need to know how long is our path. Luckily, the SVG API has a method getTotalLength() available to any SVGGeometryElement that returns the total length of our element!

âš  The SVGGeometryElement variable refers to all SVG tags that are created from a geometry (path, rect, circle,…) so this does not include image, filter, clip-path,… tags.

Let’s see how we can then use this function to animate a circle along a given path using the GreenSock library.

To do so, we need a JavaScript object that will contain the animated values (as gsap cannot animate number variables directly) and set a property distance to zero.
We then create a tween that will update the distance value from 0 to the total length of our path.
Finally on each frame, we retrieve a point along the path based on the animated distance value, and we update the cx and cy attributes of our circle to make it move ✨

// Create an object that gsap can animate
const val = { distance: 0 };
// Create a tween
gsap.to(val, { // Animate from distance 0 to the total distance distance: path.getTotalLength(), // Function call on each frame of the animation onUpdate: () => { // Query a point at the new distance value const point = path.getPointAtLength(val.distance); // Update the circle coordinates circle.setAttribute('cx', point.x); circle.setAttribute('cy', point.y); }
});

See the Pen Animate single element along path by Louis Hoebregts (@Mamboleoo) on CodePen.

âš  If the effect you want to achieve is just animating one element along an SVG path such as in the demo above, you could check the MotionPathPlugin by GreenSock. It will let you animate easily any DOM element from a path you provide. (plus it’s free!)

Using the points coordinates for particles

I love particles, it’s no breaking news. Which is why, when I learn a new technique I always try to implement something with them!
Let’s see how instead of a single circle moving along a path, we could make many more circles exploding like a bomb fuse 💣

The overall logic of this animation is exactly the same as before, except that on each frame we will create a new circle element and animate it. As you can see, the setup is very similar.

const svg = document.querySelector('svg');
const fuse = svg.querySelector('.fuse'); const val = { distance: 0 };
gsap.to(val, { distance: fuse.getTotalLength(), repeat: -1, duration: 5, onUpdate: () => { // Query a point at the new distance value const point = fuse.getPointAtLength(val.distance); // Create a new particle createParticle(point); }
});

The createParticle function will be called on each frame to make a new particle pop and fade out. Here are the steps of the animation:

  1. Create a new circle element and append it to the SVG
  2. Set the coordinates from the point we calculated with getPointAtLength
  3. Define a random radius and color for each
  4. Animate that particle cx & cy attributes to a random position
  5. Once the animation is complete, remove the particle from the DOM
function createParticle (point) { // Create a new circle element const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); // Prepend the element to the SVG svg.prepend(circle); // Set the coordinates of that circle circle.setAttribute('cx', point.x); circle.setAttribute('cy', point.y); // Define a random radius for each circle circle.setAttribute('r', (Math.random() * 2) + 0.2); // Define a random color circle.setAttribute('fill', gsap.utils.random(['#ff0000', '#ff5a00', '#ff9a00', '#ffce00', '#ffe808'])); // Animate the circle gsap.to(circle, { // Random cx based on its current position cx: '+=random(-20,20)', // Random cy based on its current position cy: '+=random(-20,20)', // Fade out opacity: 0, // Random duration for each circle duration: 'random(1, 2)', // Prevent gsap from rounding the cx & cy values autoRound: false, // Once the animation is complete onComplete: () => { // Remove the SVG element from its parent svg.removeChild(circle); } });
}

See the Pen Bomb fuse particles by Louis Hoebregts (@Mamboleoo) on CodePen.

âš  To make the animation prettier I’m also animating the stroke-dashoffset of the fuse to make it more realistic. You can check this article for more details on such animation.

Using the coordinates in WebGL

So far we have only animated SVG elements next to the path. But sometimes all we need are the raw coordinates from the path, but not the path itself. For example, if we want to animate particles in a 2D Canvas or in WebGL like in the animation below.

See the Pen Make it POP! by Louis Hoebregts (@Mamboleoo) on CodePen.

âš  The following chapter expects you to know how to setup and run a three.js scene.

Here are the key concepts of this animation:

  1. Get the path and its total length
  2. Loop along the path until you reach its length
  3. Get the point that exists on the index distance
  4. On each iteration, create a Vector3 at the point’s coordinates
  5. Push the vector into an array of vertices
  6. Create a geometry from the vertices
  7. Create a Points mesh and add it into your scene
// Get the Path in the DOM
const path = document.querySelector("path");
// Store the total length of the path
const length = path.getTotalLength(); // Empty array to store all vertices
const vertices = [];
// Loop along the path
for (let i = 0; i < length; i += 0.2) { // Get the coordinates of a point based on the index value const point = path.getPointAtLength(i); // Create a new vector at the coordinates const vector = new THREE.Vector3(point.x, -point.y, 0); // Randomize a little bit the point to make the heart fluffier vector.x += (Math.random() - 0.5) * 30; vector.y += (Math.random() - 0.5) * 30; vector.z += (Math.random() - 0.5) * 70; // Push the vector into the array vertices.push(vector);
}
// Create a new geometry from the vertices
const geometry = new THREE.BufferGeometry().setFromPoints(vertices);
// Define a pink material
const material = new THREE.PointsMaterial( { color: 0xee5282, blending: THREE.AdditiveBlending, size: 3 } );
// Create a Points mesh based on the geometry and material
const particles = new THREE.Points(geometry, material);
// Offset the particles in the scene based on the viewbox values
particles.position.x -= 600 / 2;
particles.position.y += 552 / 2;
// Add the particles in to the scene
scene.add(particles);

See the Pen Untitled by Louis Hoebregts (@Mamboleoo) on CodePen.

Creating a heart of particles is fun, but animating those particles is even FUNNIER 🥳

First we setup a global timeline so that all tweens will be grouped together and we’ll be able to repeat all of them once the last animation is complete.

// Create a global gsap timeline that contains all tweens
const tl = gsap.timeline({ repeat: -1, yoyo: true
});

While creating the vector, we attach a gsap tween on it so that it animates from the center of the heart. We can calculate the x & y coordinates for the start based on the SVG viewBox attributes viewBox="0 0 600 552".
Since y axis are in the other direction in SVG, we apply a negative value. (y is going up in WebGL, while in CSS & SVG it’s going down).

Each vector’s animation will have a delay that is calculated from its own distance along the path so that will create a nice flow of particles going round.

for (let i = 0; i < length; i += 0.1) { [...] // Create a tween for that vector tl.from(vector, { x: 600 / 2, // Center X of the heart y: -552 / 2, // Center Y of the heart z: 0, // Center of the scene ease: "power2.inOut", duration: "random(2, 5)" // Random duration }, i * 0.002 // Delay calculated from the distance along the path );
}

Finally, we need to update the geometry of the Points mesh on each frame as the Vector3 objects are being animated but the geometry is not aware of that.

function render() { requestAnimationFrame(render); // Update the geometry from the animated vertices geometry.setFromPoints(vertices);
}

And voilà 💖

See the Pen Create word from SVG Path – WebGL by Louis Hoebregts (@Mamboleoo) on CodePen.

What’s next?

You tell me! Now that you can extract the coordinates of points along an SVG path, try to apply those data anywhere else 🚀
What about animating lines instead of particles? Or create a new path based on random points that you pick along another one?

Explore this technique and share your results with me on Twitter, I can’t wait to see what you’ll come up with 💙

See you another time,
Mamboleoo

The post Animate Anything Along an SVG Path appeared first on Codrops.