Responsive Animations
Create animations that adapt to different screen sizes with breakpoints and matchMedia.
The SDK has no built-in breakpoint system — responsive animations are handled with native window.matchMedia(). Wrap animation initialization in a media query check, and the animation only runs when the condition matches.
Mobile-Only Animation
Use matchMedia to restrict an animation to a specific screen size:
import { Motion } from "@motion.page/sdk";
if (matchMedia("screen and (max-width: 576px)").matches) {
Motion("mobile-reveal", ".hero", {
from: { opacity: 0, y: 30 },
duration: 0.6,
ease: "power2.out",
}).onPageLoad();
} The check runs once at script execution time. If the condition doesn’t match, the animation is never registered — no overhead, no cleanup needed.
Different Animations per Screen Size
Combine multiple matchMedia checks to run different animations at different breakpoints:
import { Motion } from "@motion.page/sdk";
// Desktop: parallax scroll effect
if (matchMedia("screen and (min-width: 993px)").matches) {
Motion("hero-parallax", ".hero-image", {
from: { y: -60 },
to: { y: 60 },
duration: 1,
}).onScroll({ scrub: true });
}
// Tablet: simpler fade-in on scroll
if (
matchMedia(
"screen and (min-width: 577px) and (max-width: 992px)"
).matches
) {
Motion("hero-fade", ".hero-image", {
from: { opacity: 0 },
duration: 0.8,
ease: "power2.out",
}).onScroll({ toggleActions: "play none none none" });
}
// Mobile: no scroll animation — just fade in on load
if (matchMedia("screen and (max-width: 576px)").matches) {
Motion("hero-mobile", ".hero-image", {
from: { opacity: 0 },
duration: 0.5,
}).onPageLoad();
} Default Breakpoint Values
Motion.page uses these default breakpoints (configurable in Builder > Settings > Breakpoints):
| Device | Breakpoint |
|---|---|
| Phones | max-width: 576px |
| Tablets | max-width: 768px |
| Laptops | max-width: 992px |
| Desktop | min-width: 993px |
Use these values for consistency across SDK code and the Builder.
Disable an Animation on Mobile
To run an animation only on desktop, negate the mobile condition:
import { Motion } from "@motion.page/sdk";
// Skip on mobile — hover effects don't make sense on touch screens
if (matchMedia("screen and (min-width: 993px)").matches) {
Motion("card-hover", ".card", {
to: { y: -8, boxShadow: "0 12px 24px rgba(0,0,0,0.15)" },
duration: 0.3,
ease: "power2.out",
}).onHover({ each: true, onLeave: "reverse" });
} Handling Resize with Motion.context()
matchMedia checks run once. If the user resizes their browser window after load, the animation won’t update. For resize-aware animations, use Motion.context() with a matchMedia event listener:
import { Motion } from "@motion.page/sdk";
let ctx = Motion.context(() => {
if (matchMedia("screen and (min-width: 993px)").matches) {
Motion("desktop-anim", ".card", {
from: { opacity: 0, y: 40 },
duration: 0.6,
stagger: 0.1,
}).onScroll({ toggleActions: "play none none none" });
}
});
// Reinitialize on breakpoint crossing
const mq = matchMedia("screen and (min-width: 993px)");
mq.addEventListener("change", () => {
ctx.revert();
ctx = Motion.context(() => {
if (mq.matches) {
Motion("desktop-anim", ".card", {
from: { opacity: 0, y: 40 },
duration: 0.6,
stagger: 0.1,
}).onScroll({ toggleActions: "play none none none" });
}
});
}); ctx.revert() kills all timelines created inside the context and restores initial CSS. Re-running Motion.context() reinitializes them fresh.
For most production sites, breakpoint crossings are rare — a simple one-time matchMedia check on load is sufficient and has less overhead.
Reduced Motion
Respect the user’s system preference with prefers-reduced-motion:
import { Motion } from "@motion.page/sdk";
const prefersReduced = matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
Motion("hero", ".hero-title", {
from: prefersReduced
? { opacity: 0 } // Accessibility: fade only, no movement
: { opacity: 0, y: 60 },
duration: prefersReduced ? 0.3 : 0.9,
ease: "power2.out",
}).onPageLoad(); Or skip non-essential animations entirely:
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) {
Motion("decorative-parallax", ".bg-shape", {
from: { y: -80 },
to: { y: 80 },
duration: 1,
}).onScroll({ scrub: true });
} Builder Breakpoints
When using the Motion.page Builder, per-timeline breakpoints are configured in the Trigger Section > Enabled Breakpoints panel. The Builder wraps the generated animation code in a matchMedia check automatically — no manual code needed.
The breakpoint range selector maps to the same default values listed above. Adjust the global thresholds under Settings > Breakpoints.
See Breakpoints for the full Builder workflow.
Related
- Core Concepts —
Motion.context()for lifecycle management - Page Load Trigger — entrance animations on load
- Scroll Trigger — viewport-based triggers