Repeat & Yoyo
Loop animations with repeat count and yoyo back-and-forth playback.
See it live Loops feature pagerepeat controls how many times an animation cycles after its first play. Combine it with yoyo to reverse direction on alternating cycles, and delay to add a pause between each cycle.
Basic Repeat
Pass repeat as a plain number to run the animation that many additional times after the first play. A value of 2 plays 3 times total.
import { Motion } from "@motion.page/sdk";
Motion("bounce", ".ball", {
to: { y: -40 },
duration: 0.4,
ease: "power2.out",
repeat: 2,
}).onPageLoad();Infinite Loop
Use repeat: -1 to loop forever.
Motion("spin", ".loader", {
to: { rotate: 360 },
duration: 1,
ease: "none",
repeat: -1,
}).onPageLoad();RepeatConfig Object
For more control, pass an object with times, yoyo, and delay properties.
| Property | Type | Default | Description |
|---|---|---|---|
times |
number |
— | Additional repetitions. -1 = infinite |
yoyo |
boolean |
false |
Reverse direction on every other cycle |
delay |
number |
0 |
Seconds to pause between each cycle |
// Shorthand number
repeat: 3
// Full config object
repeat: { times: 3, yoyo: true, delay: 0.5 }Yoyo
yoyo: true reverses the animation on every alternate cycle — forward, backward, forward, backward. This creates smooth back-and-forth playback without a hard reset jump.
Motion("pulse", ".badge", {
to: { scale: 1.15 },
duration: 0.6,
ease: "power1.inOut",
repeat: { times: -1, yoyo: true },
}).onPageLoad();Without yoyo, an infinite repeat jumps back to the start after each cycle. With yoyo, the animation reverses smoothly — ideal for breathing, pulsing, and attention effects.
Repeat Delay
delay inside RepeatConfig adds a pause between cycles — distinct from the top-level delay which only delays the very first play.
Motion("blink", ".cursor", {
to: { opacity: 0 },
duration: 0.5,
ease: "power1.inOut",
repeat: { times: -1, yoyo: true, delay: 0.1 },
}).onPageLoad();This lives inside the RepeatConfig object as delay.
Finite Repeat with Yoyo
Combine a finite times count with yoyo for animations that play a set number of back-and-forth swings, then settle.
Motion("shake", ".notification", {
to: { x: 8 },
duration: 0.08,
ease: "power1.inOut",
repeat: { times: 5, yoyo: true },
}).play();5 additional cycles with yoyo = left, right, left, right, left, right — a natural shake effect.
Complete Example — All Three Properties
import { Motion } from "@motion.page/sdk";
Motion("breathe", ".hero-icon", {
to: { scale: 1.08, opacity: 0.85 },
duration: 1.8,
ease: "power1.inOut",
repeat: { times: -1, yoyo: true, delay: 0.3 },
}).onPageLoad();The icon gently expands and dims, holds for 0.3s, then reverses — repeating indefinitely.
Timeline Repeat with .withRepeat()
repeat inside AnimationConfig loops a single animation. To loop an entire multi-step timeline, use the .withRepeat() method on the Timeline instance instead.
// Per-animation repeat — only the individual tween loops
Motion("slide", ".box", {
from: { x: -100 },
to: { x: 100 },
duration: 0.8,
repeat: -1,
}).onPageLoad();
// Timeline repeat — the whole sequence loops as a group
Motion("sequence", [
{ target: ".box-a", from: { x: -100 }, duration: 0.5 },
{ target: ".box-b", from: { y: 60 }, duration: 0.5 },
])
.withRepeat({ times: -1, yoyo: true })
.onPageLoad();.withRepeat() accepts the same number | RepeatConfig as per-animation repeat.
onRepeat Callback
React to each completed cycle with the onRepeat callback. It receives the current repeat count (0-based).
Motion("counter", ".ring", {
to: { rotate: 360 },
duration: 1,
ease: "none",
repeat: -1,
onRepeat: (count) => {
console.log(`Cycle ${count + 1} complete`);
},
}).onPageLoad();Common Patterns
Attention pulse — draw focus to a CTA or notification badge:
Motion("cta-pulse", ".cta-button", {
to: { scale: 1.06, boxShadow: "0 0 0 8px rgba(102, 51, 238, 0.25)" },
duration: 0.8,
ease: "power1.inOut",
repeat: { times: -1, yoyo: true },
}).onPageLoad();Loading spinner — continuous rotation with no easing for constant speed:
Motion("spinner", ".loader-icon", {
to: { rotate: 360 },
duration: 0.9,
ease: "none",
repeat: -1,
}).onPageLoad();Looping marquee position — translate then instantly reset:
Motion("marquee", ".track", {
to: { x: "-50%" },
duration: 8,
ease: "none",
repeat: -1,
}).onPageLoad();Shake on error — finite yoyo for form validation feedback:
Motion("field-shake", ".input-error", {
to: { x: 6 },
duration: 0.07,
ease: "power1.inOut",
repeat: { times: 7, yoyo: true },
}).play();API Reference
// Shorthand
repeat?: number; // -1 = infinite, 3 = 3 extra cycles
// Full config
interface RepeatConfig {
times: number; // Additional repetitions; -1 = infinite
yoyo?: boolean; // Reverse direction each cycle (default: false)
delay?: number; // Seconds to pause between cycles (default: 0)
}
// Timeline-level repeat
tl.withRepeat(config: number | RepeatConfig): this
// Callback on each completed cycle
onRepeat?: (repeatCount: number) => void;Common Mistakes
Confusing top-level delay with RepeatConfig.delay. delay: 0.5 at the config root delays the first play only. repeat: { times: -1, delay: 0.5 } pauses 0.5s between every cycle. Both can coexist.
Expecting repeat: 3 to play 3 times total. It plays 3 additional times after the first — 4 plays total. Use repeat: 2 for 3 total plays.
Forgetting yoyo on infinite loops. repeat: -1 without yoyo creates a visible snap back to the start after each cycle. Add yoyo: true for continuous, seamless loops.
Related: Duration & Delay · Easing · Stagger