I've always been fascinated by the balance of static design and motion design.
Gradient meshes are traditionally seen as background art — static, decorative, and flat. But I wanted to explore: what happens when a gradient mesh becomes dynamic, interactive, and alive?
This project was about transforming a simple mesh into a stage for motion. Instead of being just a backdrop, it became a system where my profile image and tech stack icons orbit endlessly, almost like planets circling in a digital solar system. It served as the hero section of my 2022 personal website, setting the tone for everything that followed.
Why Gradient Mesh?
Gradient meshes are a popular tool in design because they create soft, fluid transitions between colors. They look organic, almost like light scattering or atmospheric blending.
In my case, I already had a gradient mesh illustration designed in Figma and exported as an SVG. It looked beautiful, but static. So the question was: How do I make this gradient mesh feel alive without overwhelming the design?
The answer was motion, subtle, continuous, and balanced.
Motion as Orbit
Rather than distorting the mesh itself, I decided to place objects on top of it and animate those instead.
The objects:
- My profile image
- Logos of technologies I use daily: AWS, React, Next.js, GitHub, and TypeScript
Instead of moving them in straight lines, I wanted them to orbit around the center, as if they were satellites tethered to an invisible path.
This is where GSAP's MotionPathPlugin came in.
Building the Path
Every orbit needs a path. In this case, it's just a perfect circle defined in SVG:
<svg
width="760"
height="760"
viewBox="0 0 760 760"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
ref={animatePathRef}
id="orbit-path"
opacity="0.2"
d="M379.922 0.631836C589.435 0.631836 759.369 170.407 759.369 380C759.369 589.513 589.435 759.447 379.922 759.447C170.408 759.368 0.632812 589.513 0.632812 379.921C0.632812 170.407 170.408 0.631836 379.922 0.631836Z"
stroke="white"
strokeOpacity="0.3"
strokeWidth="2"
/>
</svg>
This is just math: A circle centered at (380, 380) with a radius of ~379px.
This invisible circle is what drives all the animations.
GSAP MotionPathPlugin
Now the fun part: GSAP's MotionPathPlugin.
The plugin allows us to map any element to follow an SVG path. No need to calculate angles, positions, or Bézier curves manually, GSAP does all the geometry.
First, I registered the plugin and created a looping timeline:
gsap.registerPlugin(MotionPathPlugin);
const tl = gsap.timeline({
repeat: -1, // infinite loop
yoyo: true, // goes forward, then backward
defaults: {
duration: 90,
ease: "none", // constant speed
},
});
Notice the duration: 90s. That's intentionally slow, almost meditative. I didn't want jittery or distracting motion.
Aligning Elements
Before animating, all icons are centered with xPercent / yPercent and transformOrigin:
tl.set(
[
profileRef.current,
awsRef.current,
nextjsRef.current,
reactRef.current,
githubRef.current,
typescriptRef.current
],
{
xPercent: -50,
yPercent: -50,
transformOrigin: "50% 50%",
}
);
This ensures the icons rotate around their own centers instead of offsetting awkwardly.
Offsetting Each Orbit
If all elements started at the same position, they'd overlap. So each one gets a different start and end offset along the path.
tl.to(profileRef.current, {
motionPath: {
path: animatePathRef.current,
align: animatePathRef.current,
autoRotate: false,
start: 0,
end: 0.5,
},
}, 0);
tl.to(awsRef.current, {
motionPath: {
path: animatePathRef.current,
start: 0.18,
end: 0.65,
},
}, 0);
tl.to(reactRef.current,
{
motionPath: {
path: animatePathRef.current,
start: 0.32,
end: 0.82
},
}, 0);
tl.to(githubRef.current, {
motionPath: {
path: animatePathRef.current,
start: 0.5,
end: 1,
},
}, 0);
tl.to(nextjsRef.current, {
motionPath: {
path: animatePathRef.current,
start: 0.66,
end: 1.178,
},
}, 0);
tl.to(typescriptRef.current, {
motionPath: {
path: animatePathRef.current,
start: 1.8,
end: 2.33,
},
}, 0);
Each start / end value represents a percentage along the orbit. By staggering them, I created natural spacing so icons feel like independent satellites.
The Visual Effect
When combined, the result is:
- Icons slowly orbit around the gradient mesh
- They move at the same speed, but are offset in position
- The yoyo: true timeline makes them oscillate back and forth
The mesh background stays static while the orbiting creates motion contrast. It feels alive, but not chaotic.
Design Considerations
Subtlety over Flashiness
- Motion should enhance, not overwhelm.
- That's why the animation runs for 90s per loop.
Circular Path vs Random Path
- I tested irregular Bézier paths, but they felt distracting.
- The circle creates harmony and predictability.
Layering Icons
- Different icons overlap naturally at times.
- This adds depth, almost like orbital parallax.
Balance with Static Mesh
- The mesh itself doesn't move.
- This contrast makes the animation stand out more.
Key Takeaways
- GSAP MotionPathPlugin is perfect for path-based animations without heavy math.
- Using different path offsets makes multiple elements move together without collisions.
- Slow, looping motion feels polished and professional compared to fast spins.
- Static backgrounds + dynamic motion = balanced design.