How to add scroll-triggered animations to a portfolio using IntersectionObserver
IntersectionObserver API, threshold, rootMargin, fade-in animation, CSS opacity and transform, observe and unobserve, performance vs scroll events
Animate on Scroll Without Performance Issues
Scroll-triggered animations make a portfolio feel polished. The correct tool is IntersectionObserver — not scroll event listeners, which fire hundreds of times per second and kill performance.
CSS: Hidden Initial State
.fade-in {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.5s ease, transform 0.5s ease;
}
.fade-in.visible {
opacity: 1;
transform: translateY(0);
}JavaScript: IntersectionObserver
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target); // Animate once only
}
});
}, { threshold: 0.15 });
document.querySelectorAll('.fade-in').forEach(el => {
observer.observe(el);
});Add class="fade-in" to section headings and project cards. The threshold: 0.15 option triggers the animation when 15% of the element is visible.
Call observer.unobserve() after adding the visible class so the observer stops watching elements that have already animated. This avoids unnecessary callbacks and keeps the observer lean. Always respect prefers-reduced-motion by wrapping the observer setup in a motion check.
