This is the most upvoted Lottie question on Stack Overflow. The usual mistake is listening for complete on an animation that has loop set to true, which never ends.
lottie-web (plain JavaScript)
import lottie from 'lottie-web';
const anim = lottie.loadAnimation({
container: document.getElementById('checkmark'),
renderer: 'svg',
loop: false,
autoplay: true,
path: '/animations/checkmark.json'
});
anim.addEventListener('complete', () => {
console.log('done');
});
// for looping animations
anim.addEventListener('loopComplete', () => {});React (lottie-react)
import Lottie from 'lottie-react';
import checkmark from './checkmark.json';
<Lottie
animationData={checkmark}
loop={false}
onComplete={() => setShowNext(true)}
/>dotLottie web
dotLottie.addEventListener('complete', () => {});Android and iOS
On Android with the View system, call animationView.addAnimatorListener and handle onAnimationEnd. In Jetpack Compose, read the progress from animateLottieCompositionAsState and react when it reaches 1f. On iOS with lottie-ios, call animationView.play { finished in } and check that finished is true, because it is false when the animation was interrupted.
If you find yourself chaining many completion handlers to move between states (play intro, then idle, then success), that is a state machine written by hand. Tools with built-in state machines, such as dotLottie state machines, Rive or ExodeUI, can hold that logic in the file. We make ExodeUI.