Lottie Setup
Lazy-loads Lottie JSON animations with GSAP ScrollTrigger — each animation initialises only when half a viewport away from entering, plays while visible, and pauses on leave. Respects prefers-reduced-motion by rendering a single static frame instead of playing.
Setup — External Scripts
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/ScrollTrigger.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lottie-web@5.12.2/build/player/lottie.min.js"></script>Code
<div class="lottie-demo-card">
<span class="lottie-demo-card__tag">Animation #001</span>
<div data-lottie-frame="10" data-lottie data-lottie-src="https://osmo.b-cdn.net/resource-media/lottie-demo-piggy-1750244874581.json" class="lottie-demo-card__animation"></div>
<h2 class="lottie-demo-card__heading">Save money</h2>
</div>.lottie-demo-card {
aspect-ratio: 1 / 1.25;
background-color: #fff;
border-radius: 1.5rem;
flex-flow: column;
justify-content: flex-end;
align-items: flex-start;
width: 26.5rem;
padding: 2rem;
display: flex;
position: relative;
}
.lottie-demo-card__heading {
margin: 0;
font-size: 2rem;
font-weight: 500;
line-height: 1.2;
}
.lottie-demo-card__animation {
z-index: 1;
position: absolute;
top: 2rem;
left: 2rem;
right: 2rem;
}
.lottie-demo-card__tag {
z-index: 2;
background-color: #f7e8d3;
border-radius: 100em;
padding: .5em .75em;
font-size: .875rem;
line-height: 1;
position: absolute;
top: 2rem;
left: 2rem;
}gsap.registerPlugin(ScrollTrigger);
function initLottieAnimations() {
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
document.querySelectorAll("[data-lottie]").forEach(target => {
let anim; // Store animation reference
ScrollTrigger.create({
trigger: target,
start: "top bottom+=50%", // Load lottie once it's half a viewport away from entering
end: "bottom top-=25%",
onEnter: handleEnter,
onEnterBack: handleEnter,
onLeave: handleLeave,
onLeaveBack: handleLeave,
});
function handleEnter() {
// Handle first enter: create a lottie player
if (!target.hasAttribute("data-lottie-fired")) {
target.setAttribute("data-lottie-fired", "true");
anim = lottie.loadAnimation({
container: target,
renderer: "svg",
loop: true,
autoplay: !reduceMotion,
path: target.getAttribute("data-lottie-src"),
});
// If reduced motion is ON, load the first frame as a static SVG image
// Add [data-lottie-frame] with the number of your desired frame if not 0
anim.addEventListener("DOMLoaded", () => {
if (reduceMotion) {
const frame = parseInt(target.getAttribute("data-lottie-frame") || "0", 10);
anim.goToAndStop(frame, true);
}
});
} else if (anim && !reduceMotion) {
// If lottie enters view again, and is already created, simply play it
anim.play();
}
}
function handleLeave() {
if (anim && !reduceMotion) {
anim.pause();
}
}
});
}
// Initialize Lottie Setup
document.addEventListener("DOMContentLoaded", () => {
initLottieAnimations();
});Guide
How It Works
The script initialises a Lottie player only when the target element is within half a viewport of entering the screen, avoiding unnecessary payload on page load. Once created, animations play while in view and pause automatically when scrolled out.
data-lottie
Required. Add to any element (typically an empty div) to mark it as a Lottie target. Width should be set via CSS; height is determined by the Lottie file itself.
data-lottie-src
Required. Set this to the URL or path of your .json animation file on the same element as data-lottie.
data-lottie-frame
Optional. Specifies which frame to display as a static image when the user has prefers-reduced-motion enabled. Defaults to frame 0 if omitted.
Reduced Motion
When prefers-reduced-motion is active, the animation is loaded but immediately stopped at the specified frame (or frame 0), rendering it as a static SVG. autoplay is also disabled so the animation never starts.
Performance
Animations pause when they leave the viewport, reducing CPU/GPU load for off-screen content. The data-lottie-fired attribute is used as a flag to ensure lottie.loadAnimation is only called once per element.