Lottie Integration
Sync Lottie Player animations with Motion.page timelines for scroll-driven and triggered playback.
Lottie Integration syncs a <lottie-player> (or <dotlottie-player>) element’s frame playback with any Motion.page timeline. As the timeline progresses, the Lottie animation scrubs through its frames — making scroll-driven Lottie playback a natural fit.
Prerequisites
- Load the Lottie Player script on your page before Motion.page initialises:
<!-- @lottiefiles/lottie-player -->
<script src="https://unpkg.com/@lottiefiles/lottie-player@latest/dist/lottie-player.js"></script> <!-- or dotLottie Player -->
<script src="https://unpkg.com/@dotlottie/player-component@latest/dist/dotlottie-player.js"></script> - Add a
<lottie-player>element to your page and give it a CSS selector you can target:
<lottie-player
id="hero-lottie"
src="/animations/hero.json"
background="transparent"
speed="1"
></lottie-player> - Use that selector as the animation target in your Motion config.
Basic Usage
Add a lottie object to your animation config. The SDK generates an onUpdate callback that scrubs the animation’s frames as the timeline progresses.
import { Motion } from "@motion.page/sdk";
Motion("hero-anim", "#hero-lottie", {
duration: 1,
lottie: {
frames: [0, 1],
},
}).onPageLoad(); frames is a normalized range — 0 is the first frame, 1 is the last frame. [0, 1] plays the full animation.
Note:
lottieis a root-level property on the animation config — it is not placed insidefromorto. Standardfrom/toproperties are not needed for Lottie animations;durationcontrols the timeline length used for scrubbing.
Options
| Option | Type | Default | Description |
|---|---|---|---|
frames | [number, number] | [0, 1] | Normalized frame range. 0 = first frame, 1 = last frame |
reverse | boolean | false | Play the frame range in reverse as the timeline progresses |
Scroll-Synced Playback
The most common pattern: scrub the Lottie animation as the user scrolls. As scroll progress goes from 0 → 1, the animation plays from its first frame to its last.
Motion("scroll-lottie", "#hero-lottie", {
duration: 1,
lottie: {
frames: [0, 1],
},
}).onScroll({ scrub: true }); Control the scroll range using start and end:
Motion("scroll-lottie", "#hero-lottie", {
duration: 1,
lottie: {
frames: [0, 1],
},
}).onScroll({
scrub: true,
start: "top center",
end: "bottom center",
}); Partial Frame Range
Use a sub-range of frames instead of the full animation. Values are normalized 0–1, where 0.25 means 25% through the total frame count.
// Only play the middle half of the animation (frames 25%–75%)
Motion("partial-lottie", "#feature-lottie", {
duration: 1,
lottie: {
frames: [0.25, 0.75],
},
}).onScroll({ scrub: true }); Reverse Playback
Set reverse: true to play frames in reverse direction as the timeline progresses forward. The frame range is preserved — only the direction is swapped.
// Plays from last frame to first frame as scroll advances
Motion("reverse-lottie", "#icon-lottie", {
duration: 1,
lottie: {
frames: [0, 1],
reverse: true,
},
}).onScroll({ scrub: true }); frames | reverse | Result |
|---|---|---|
[0, 1] | false | First frame → last frame |
[0, 1] | true | Last frame → first frame |
[0.25, 0.75] | false | 25% frame → 75% frame |
[0.25, 0.75] | true | 75% frame → 25% frame |
Triggered Playback
Lottie animations work with any Motion.page trigger — not just scroll. Use .onPageLoad(), .onHover(), or .onClick() to drive the timeline (and therefore the Lottie frames) with those interactions.
// Play through all frames once on page load
Motion("load-lottie", "#intro-lottie", {
duration: 2,
lottie: {
frames: [0, 1],
},
}).onPageLoad(); // Scrub through frames on hover, reverse on leave
Motion("hover-lottie", ".card-lottie", {
duration: 0.6,
lottie: {
frames: [0, 1],
},
}).onHover({ each: true, onLeave: "reverse" }); How It Works
The integration bridges Motion.page timelines to lottie-web’s API. The SDK generates an onUpdate callback attached to the timeline. On every tick, it:
- Reads
el._lottie.totalFrames— lottie-web exposes itsAnimationItemdirectly on the DOM element as._lottie - Calculates the target frame:
startFrame + (endFrame - startFrame) * progress - Calls
el._lottie.goToAndStop(frameIndex, true)to seek without playing
The equivalent manual implementation for a full-range scrub:
Motion("manual-lottie", "#hero-lottie", {
duration: 1,
onUpdate: (p) => {
document.querySelectorAll("#hero-lottie").forEach((el) => {
if (el._lottie) {
const t = el._lottie.totalFrames - 1;
el._lottie.goToAndStop(Math.round(p * t), true);
}
});
},
}).onScroll({ scrub: true }); Using the lottie config option generates this callback automatically.
Limitations
Lottie and Timeline Control’s onUpdate cannot be combined. Both use the timeline’s .onUpdate() slot. If a Lottie animation is present in a timeline, any custom onUpdate set via Timeline Control is suppressed for that timeline.
See Timeline Control for details on the onUpdate slot.
Common Patterns
Full-Page Scroll Story
Pin a section and scrub the Lottie through its full animation while the user scrolls through the pinned section.
Motion("story-lottie", "#story-lottie", {
duration: 1,
lottie: {
frames: [0, 1],
},
}).onScroll({
scrub: true,
pin: true,
start: "top top",
end: "+=200%",
}); Multiple Lottie Sections
Each section plays a different frame range of the same animation — useful for step-by-step product demos.
// Step 1: frames 0–33%
Motion("step-1", "#product-lottie", {
duration: 1,
lottie: { frames: [0, 0.33] },
}).onScroll({ scrub: true, start: "top 80%", end: "center center" });
// Step 2: frames 33–66%
Motion("step-2", "#product-lottie", {
duration: 1,
lottie: { frames: [0.33, 0.66] },
}).onScroll({ scrub: true, start: "center center", end: "bottom center" }); Lottie Icon on Hover
Animate a Lottie icon through a short loop on hover.
Motion("icon-hover", ".nav-icon lottie-player", {
duration: 0.5,
lottie: {
frames: [0, 1],
},
}).onHover({ each: true }); See Also
- Scroll Trigger — trigger and scrub animations based on scroll position
- Scroll Trigger Advanced — pin, snap, and horizontal scroll
- Timeline Control — programmatic playback control