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.

lottiegsapscroll-triggeranimationperformanceaccessibility

Setup — External Scripts

Setup: External Scripts
html
<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

index.html
html
<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>
styles.css
css
.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;
}
script.js
javascript
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.