Distribute cards evenly on curved path and animate on scroll using GSAP plugins
In this tutorial, we’ll learn how to animate cards along a curved path and trigger that animation based on scroll — using GSAP, ScrollTrigger, and the MotionPathPlugin.
You’ve probably seen this kind of animation on modern websites — especially in fashion e-commerce or platforms that use cards to display content. These cards often animate in a carousel format, shuffle in place, or follow a path-based transition. While this demo is simple, it unlocks possibilities for countless modern UI animations. Below is the demo CodePen
See the Pen cards move on curve GSAP by trapti (@tripti1410) on CodePen.
The Curved Path
To begin, we need a path along which the cards will move. There are two common ways to define a motion path in GSAP:
- Using coordinates directly via GSAP’s
motionPath
plugin. - Using an SVG path, which is what we’ll use here.
We’ll embed the SVG in the HTML and refer to its #id
or .class
in the animation.
The Cards
We place 8 cards inside a container called .card-container
. Each card will be individually animated along the curved path.
Setup
Wrap the SVG and the card container inside a parent element called .motion-wrapper
. To make the cards appear on the path visually, we overlap the SVG using position: absolute
.
Aligning Cards to the curved path using motionPath
The MotionPathPlugin
helps us easily align the cards to the curved SVG path. Here’s the basic logic to align and animate each card.
Pick all cards and loop through them using forEach
. Animate the cards with gsap.to
tween, using motionPath to curve them. I also centered the cards using alignOrigin: [0.5, 0.5]
along the curved line. autoRotate
will make the card rotate correctly.
const cards = gsap.utils.toArray(".item");
cards.forEach((card, i) => {
gsap.to(card, {
motionPath: {
path: ".curve-path",
align: ".curve-path",
alignOrigin: [0.5, 0.5],
autoRotate: true,
},
});
});
Doing so cards will overlap at the start of the curved path. Like it is shown below.
Evenly Distributing Cards Along a Curved Path with MotionPath
When distributing cards along a curved path with equal spacing, we usually need to do some math — especially to determine the total length of that path. Since the path can be of any shape or size, calculating the exact length manually would be tedious.
Thankfully, the motionPath
plugin simplifies this process for us. Instead of calculating the path length ourselves, the plugin abstracts it using a normalized scale:
0
represents the start of the path1
represents the end of the path
We can use these normalized values in the plugin’s properties like start
and end
to control the positioning of elements along the curve. By default, start
is set to 0
and end
is set to 1
, giving us an intuitive way to distribute items along any path — no manual length calculations required.
In the below image I have drawn an example how we distribute cards along a straight line of length 14 and there are 8 cards.
On a curved path, we follow the same logic but with normalized values:
- Total length of curved path = 1
- Total number of cards = 8
- Total number of spacing = 8 - 1 = 7
- All the cards are aligned center
We know first card is 0 and last card is 1. For others cards will use below math where i
is the card position as we will be looping through all the cards. We use i - 1
because the first and last cards are centered, so part of them sits outside the path bounds.
Utility Function to Calculate Card Start Position
Here’s the function to compute each card’s start
value on the path:
function getStartValues(i, totalCards) {
if (i + 1 === 1) {
// First card so value should be 1 but if we make exact one that autororate does not take effect so reduced it with marginal difference.
return 0.99;
} else if (i + 1 === 8) {
return 0;
} else {
//totalCards - 1 because last and first cards half portion is out as we are aligning it from the center of the card
return (totalCards - (i + 1)) * (1 / (totalCards - 1));
}
}
We are using end: 1
I am animating it from start to end position. But this can be animated in various ways.
cards.forEach((card, i) => {
gsap.to(card, {
scrollTrigger: {
trigger: ".motion-container",
start: () => "top top",
end: () => "bottom+=300 center",
scrub: 1,
markers: true,
},
ease: "none",
motionPath: {
path: ".curve-path",
align: ".curve-path",
alignOrigin: [0.5, 0.5],
autoRotate: true,
start: getStartValues(i, totalCards), // Distribute evenly along the path
end: 1,
},
});
});
Scroll-Based Animation with ScrollTrigger
We animate the cards on scroll using ScrollTrigger
. One ScrollTrigger pins the section, while another is used per card to scrub the animation along the path. A detailed explanation of scrollTrigger is beyond the scope of this blog, which focuses on illustrating the distribution concept along a curved path.
All the code and animation be seen on the CodePen demo.
Wrapping Up
This blog focused on animating cards along a motion path, evenly spaced and controlled by scroll. While we didn’t dive deep into ScrollTrigger
mechanics here, this setup shows the power of combining GSAP’s tools to build sleek, interactive experiences.
A similar concept using a curved carousel on scroll is explored in another post — [coming soon].