Static websites are fine. Functional websites are better. But websites that feel alive? That’s where the magic happens. Here’s how I transformed this portfolio from a collection of pages into a playground of delightful interactions using GSAP.
🎪 The Philosophy: Playful, Not Gimmicky
Before diving into code, let’s talk approach. Interactive elements should enhance the experience, not distract from it. Every animation here serves a purpose:
- Float animations: Create ambient, calming movement
- Hover effects: Provide satisfying feedback
- Magnetic interactions: Make elements feel responsive and alive
- 3D transforms: Add depth and premium feel
The goal? Make users smile without overwhelming them.
✨ Floating Elements: Ambient Life
The hero images on the homepage gently float and rotate, creating a sense of continuous motion without demanding attention.
export const initFloatingElements = () => {
const elements = document.querySelectorAll(".float-animate");
elements.forEach((el, index) => {
// Create orbital motion
const tl = gsap.timeline({ repeat: -1 });
tl.to(el, { y: -30, duration: 2, ease: "sine.inOut" })
.to(el, { x: 20, duration: 1.5, ease: "sine.inOut" }, "-=1")
.to(el, { y: 0, duration: 2, ease: "sine.inOut" })
.to(el, { x: 0, duration: 1.5, ease: "sine.inOut" }, "-=1");
// Independent rotation
gsap.to(el, {
rotation: "random(-15, 15)",
duration: "random(4, 6)",
repeat: -1,
yoyo: true,
ease: "sine.inOut",
delay: index * 0.3,
});
});
};
Why this works:
- Circular motion path feels natural
- Independent rotation adds complexity
- Staggered delays prevent synchronization
- Slow speeds (4-6s) keep it calming
Just add class="float-animate" to any element and it comes to life!
🎯 3D Hover Lift: Premium Card Effects
The CTACard components now tilt in 3D space based on your cursor position. Move your mouse across a card and watch it follow you with depth and shadows.
el.addEventListener("mousemove", (e) => {
const rect = el.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
// Calculate tilt angles
const rotateX = (y - centerY) / 15;
const rotateY = (centerX - x) / 15;
gsap.to(el, {
rotationX: rotateX,
rotationY: rotateY,
y: -20,
scale: 1.05,
boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.4)",
filter: "brightness(1.1) saturate(1.2)",
duration: 0.2,
ease: "power1.out",
});
});
The secret sauce:
rotationXandrotationYfollow cursor position- Dynamic shadows create depth perception
- Brightness/saturation boost on hover
elastic.outeasing on mouse leave for satisfying bounce-back
CSS requirements:
.card-container {
perspective: 1000px;
}
.card {
transform-style: preserve-3d;
}
🧲 Magnetic Buttons: The Pull Effect
Buttons and links now have a magnetic quality - they subtly pull toward your cursor as you approach, with dynamic strength based on distance.
el.addEventListener("mousemove", (e) => {
const rect = el.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const deltaX = e.clientX - centerX;
const deltaY = e.clientY - centerY;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Stronger pull when closer
const pullStrength = Math.max(0, 1 - distance / 150);
const x = deltaX * 0.4 * pullStrength;
const y = deltaY * 0.4 * pullStrength;
gsap.to(el, {
x: x,
y: y,
rotation: (deltaX / rect.width) * 8,
scale: 1.1 + (pullStrength * 0.05),
duration: 0.2,
ease: "power2.out",
});
});
Physics-inspired design:
- Magnetic radius of 150px
- Pull strength inversely proportional to distance
- Rotation adds dynamic feel
- Scale increases as cursor approaches
Add class="magnetic" to any button and it becomes irresistible!
📌 Sticky Notes & Tooltips: Playful Annotations
Hover over interactive elements to see fun sticky note popups that guide and delight users.
function createSticker(element, message) {
const sticker = document.createElement('div');
sticker.className = 'sticker-tooltip';
sticker.textContent = message;
// Random offset for variety
const offsetX = (Math.random() - 0.5) * 20;
const offsetY = -60 + (Math.random() - 0.5) * 10;
// Position and animate
gsap.fromTo(sticker,
{
opacity: 0,
y: 10,
rotation: Math.random() * 10 - 5,
scale: 0.8
},
{
opacity: 1,
y: 0,
scale: 1,
duration: 0.4,
ease: "back.out(2)"
}
);
}
What makes it fun:
- Random rotation for authentic sticky note feel
back.outeasing creates overshoot effect- Auto-targets
.draggable-item,.hover-lift, and.skill-badge - 300ms delay before showing (feels intentional)
Messages include gems like “Drag me around! 🎯” and “I’m interactive! 🎪”
🎨 The Tech Stack
All of this magic is powered by:
GSAP (GreenSock Animation Platform)
- Professional-grade animation library
- 60fps performance
- Advanced easing functions (elastic, back, bounce)
- Timeline sequencing
- 3D transforms with proper perspective
Implementation Structure:
src/scripts/
├── gsap-config.js # GSAP setup & safe animation wrapper
├── interactive-animations.js # Hover, magnetic, float effects
└── init-animations.js # Initialization orchestration
Why GSAP over CSS?
- Dynamic calculations (cursor tracking, distance-based strength)
- Complex easing functions unavailable in CSS
- Timeline choreography
- Better performance for complex animations
- Programmatic control
🎯 Usage Examples
Floating Images
<Image
src={heroImage}
class="float-animate"
alt="Project showcase"
/>
3D Hover Cards
<article class="card hover-lift">
<h2>Featured Project</h2>
<p>Description here...</p>
</article>
Magnetic Buttons
<a href="/about" class="btn magnetic">
Get to Know Me
</a>
Draggable Elements
<div class="skill-card draggable-item">
<h3>Design Tools</h3>
</div>
♿ Accessibility First
All animations respect user preferences:
// Check for reduced motion
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
if (!prefersReducedMotion) {
initAllAnimations();
}
@media (prefers-reduced-motion: reduce) {
.float-animate,
.magnetic,
.hover-lift {
animation: none !important;
transition: none !important;
}
}
Users who prefer reduced motion get a fully functional site without the dizzying effects.
📊 Performance Considerations
What we do right:
- Hardware-accelerated transforms (
translate3d,scale,rotate) will-changehints for frequently animated properties- Debounced scroll listeners
- Conditional loading based on viewport
- No layout thrashing
Measured impact:
- First Contentful Paint: Unchanged
- Time to Interactive: +50ms (negligible)
- Total Blocking Time: Minimal
- 60fps maintained on all animations
The site loads fast and runs smooth. Best of both worlds.
🎪 The Result
These interactions transform the portfolio from a resume into an experience. Visitors don’t just read about my work - they feel the attention to detail and playful approach that defines my design philosophy.
User feedback highlights:
- “This site feels premium”
- “I keep coming back just to play with the animations”
- “The magnetic buttons are so satisfying”
- “It’s professional but fun”
🚀 What’s Next?
Future animation enhancements could include:
- Scroll-triggered animations: Elements animate in as you scroll
- Page transitions: Smooth morphing between routes
- Cursor trails: Custom cursor with particle effects
- Parallax sections: Depth through layered scrolling
- Loading animations: Skeleton screens and progress indicators
🛠️ Want to Try It?
All the code is modular and reusable. Check out the implementation:
- Install GSAP:
npm install gsap - Import the modules:
import { initAllAnimations } from './init-animations.js' - Add classes:
float-animate,hover-lift,magnetic,draggable-item - Initialize:
initAllAnimations()in your script
Or explore the GitHub repository to see it all in action!
The takeaway? Animations aren’t decoration - they’re part of the design language. When done right, they communicate personality, provide feedback, and make digital experiences memorable.
Now go forth and yassify your own projects! ✨🎨