i deleted framer motion and nothing broke
My portfolio used to run on Framer Motion. Every hover effect, every entrance animation, every subtle transition—powered by a 50kb JavaScript library.
Then I noticed animations stuttering on my old MacBook Air. JavaScript animations run on the main thread, competing with rendering and user input. CSS transforms run on the GPU. Different thread, smoother animations.
So I deleted Framer Motion. The site got faster and nothing broke.
the conversion
25 components. Same pattern every time: replace Framer Motion components with HTML elements, swap animation props for CSS classes.
Hover effects:
// before
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
>
click me
</motion.button>
// after
<button className="hover:scale-[1.02] active:scale-[0.98] transition-transform duration-200">
click me
</button>Entrance animations needed a hook:
export function useEntranceAnimation(delay = 0) {
const [isVisible, setIsVisible] = useState(false)
const ref = useRef(null)
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
setTimeout(() => setIsVisible(true), delay)
observer.unobserve(entries[0].target)
}
},
{ threshold: 0.1, rootMargin: '-40% 0px' }
)
if (ref.current) observer.observe(ref.current)
return () => observer.disconnect()
}, [delay])
return {
ref,
className: isVisible
? 'opacity-100 translate-y-0'
: 'opacity-0 translate-y-5'
}
}Intersection Observer detects viewport entry. CSS classes trigger transitions. GPU handles the actual animation.
the hard part
AnimatePresence was tricky. Framer Motion handles mount/unmount transitions automatically. I had to build a custom component that manages visibility state and applies CSS transitions during both enter and exit phases.
Worth it though. No more framework magic I couldn't debug.
the results
| Metric | Before | After |
|---|---|---|
| Bundle size | +50kb | 0 |
| Animation thread | Main | GPU |
| Scroll stuttering | Sometimes | Never |
| Hover jank | Occasional | None |
First Contentful Paint improved. Time to Interactive improved. The site just feels snappier.
bonus: centralized timing
All animations now use CSS custom properties:
:root {
--motion-base: 200ms;
--motion-ease-ios: cubic-bezier(0.22, 1, 0.36, 1);
}
.animate {
transition: transform var(--motion-base) var(--motion-ease-ios);
}One variable change adjusts the feel of every animation on the site.
when framer motion makes sense
Complex gesture sequences. Physics-based springs. Coordinated multi-element animations. If you're building something like Stripe's homepage, use Framer Motion.
For a portfolio with hover effects and fade-ins? CSS is enough. The platform already has everything you need.
50kb sounds small. But it's 50kb to download, parse, and execute on every visit. Dependencies compound. Sometimes the best code is the code you delete.