Bouncy Card Hover: A Tiny CSS Detail That Makes a Difference
Leroy · 1 Jun 2026 · 2 min read
The Bounce
Hover over a card on this site and it lifts up with a bounce. It scales slightly. It casts a deeper shadow. A tiny detail that makes the interface feel responsive.
The effect uses GSAP, which is already loaded on the site for the gooey menu. Since GSAP is available globally, adding card animations was a few lines of code:
document.querySelectorAll('.card-hover').forEach(function(card) {
card.addEventListener('mouseenter', function() {
gsap.to(card, {
y: -8, scale: 1.02,
boxShadow: '0 20px 40px rgba(0,0,0,0.2)',
duration: 0.3,
ease: 'power2.out',
overwrite: 'auto'
});
});
card.addEventListener('mouseleave', function() {
gsap.to(card, {
y: 0, scale: 1,
boxShadow: 'none',
duration: 0.2,
ease: 'power2.out',
overwrite: 'auto'
});
});
});
On mouseenter the card lifts by 8 pixels, scales up 2%, and gains a shadow. All in 300ms with GSAP's power2.out easing for smooth deceleration. On mouseleave it snaps back in 200ms.
The overwrite: auto flag stops rapid hover transitions from queuing up. Each new hover cancels the previous animation cleanly.
CSS Fallback
I keep a pure CSS version as the baseline. It only applies on devices with a real cursor:
.card-hover {
will-change: transform, box-shadow;
}
@media (hover: hover) {
.card-hover:hover {
transform: translateY(-4px);
box-shadow: 0 8px 25px rgba(0,0,0,0.08);
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 0.3s ease;
}
}
The CSS version serves two purposes. It is a fallback if GSAP fails to load. And it provides the will-change hint so the browser can optimize for the GSAP animation. The cubic-bezier 0.34, 1.56, 0.64, 1 gives the springy overshoot.
The Easing Curve
The easing is the same in CSS and GSAP. A standard ease or ease-out feels linear and robotic. cubic-bezier(0.34, 1.56, 0.64, 1) has a spring-like overshoot. The second value (1.56) is greater than 1, so the animation overshoots its final position before snapping back.
| Easing | Feels like |
|---|---|
ease |
Default, flat |
ease-out |
Smooth deceleration |
cubic-bezier(0.34, 1.56, 0.64, 1) |
Springy bounce |
Why GSAP Instead of Pure CSS?
CSS transitions interpolate between two states. GSAP gives me three things that are harder with CSS alone.
- Composing
yandscaletogether. CSStransform: translateY(-8px) scale(1.02)works, but GSAP lets me animate each property independently with different easings if needed. - Animating
boxShadowas a number. CSS can transition box-shadow, but it is clunky. GSAP treats it as a numeric value and interpolates smoothly. overwrite: auto. Clean handling of rapid hover-ins and hover-outs without animation queue buildup.
Accessibility
The animation respects prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
.card-hover {
transition: none;
}
.card-hover:hover {
transform: none;
box-shadow: none;
}
}
If a user prefers reduced motion, the cards display their default state with no transform or shadow change.
The Result
Hover over the cards on the homepage or the blog listing. Each lifts with a springy bounce. A small touch that makes browsing feel more connected.