8 min read
Scroll-Triggered and Interactive Lottie Animations
Most Lottie animations just autoplay in a loop, but the player APIs expose enough control to drive an animation from scroll position, hover state, or a click — without needing separate animation files for each state.
Play once when it scrolls into view
The most common pattern: don't autoplay immediately on page load, wait until the container is actually visible using IntersectionObserver, then play once.
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
animation.play();
observer.unobserve(entry.target); // only trigger once
}
});
},
{ threshold: 0.5 }
);
observer.observe(document.getElementById('lottie-container'));Scrubbing: tie animation progress to scroll position
Instead of playing forward automatically, you can map scroll progress directly to a frame number and call goToAndStop on every scroll event, giving the effect of the animation being "attached" to the scrollbar. This is how a lot of product-page scroll-hijack animations work.
function updateFrame() {
const rect = container.getBoundingClientRect();
const progress = Math.min(
Math.max((window.innerHeight - rect.top) / (window.innerHeight + rect.height), 0),
1
);
const frame = Math.floor(progress * animation.totalFrames);
animation.goToAndStop(frame, true); // true = frame value, not milliseconds
}
window.addEventListener('scroll', () => requestAnimationFrame(updateFrame));Always wrap the scroll handler in requestAnimationFrame (or throttle it) — calling goToAndStop synchronously on every raw scroll event will visibly stutter on lower-end devices.
Click and hover: playing specific segments
If your animation has distinct sections — an idle loop, a "success" burst, an "error" shake — playSegments lets you jump to and play just that range of frames without loading a separate file.
button.addEventListener('click', () => {
// play frames 30 to 90, then stop (don't loop back to idle automatically)
animation.playSegments([30, 90], true);
});
button.addEventListener('mouseenter', () => animation.setDirection(1));
button.addEventListener('mouseleave', () => animation.setDirection(-1));