Skip to content
Anime.js Animation Controls & Timelines — Practical Guide

Anime.js Animation Controls & Timelines — Practical Guide

DodaTech Updated Jun 6, 2026 10 min read

Anime.js animation controls let you start, stop, reverse, and seek through animations on demand, while timelines let you sequence multiple animations with precise timing — turning simple movements into orchestrated choreography.

What You’ll Learn

By the end of this tutorial, you’ll control animations with .play(), .pause(), .reverse(), .seek(), and .restart(), understand callbacks (begin, update, complete), build timelines with anime.timeline(), and sequence complex multi-step animations using promises.

Before starting: You should understand the basics from the Getting Started tutorial — especially how anime() creates animations.

Why Animation Controls Matter

A simple “move from A to B” animation is nice, but real-world interfaces need more: a button that pauses the animation, a slider that seeks to a specific moment, a “reverse” button that plays it backward. Without control methods, you’d have to destroy and recreate the animation every time.

Timelines solve a different problem: sequencing. Imagine a product page where the image fades in, then the title slides down, then the description appears. Without a timeline, you’d chain callbacks or use nested timeouts — messy and imprecise. Timelines give you clean, declarative sequencing.

Real-world use: Durga Antivirus Pro uses Anime.js timelines for its animated scan results — first the shield icon pulses, then the progress bar fills, then status text fades in, each step precisely timed. The .reverse() control lets users “rewind” the animation to watch it again.

Where This Fits in Your Learning Path

    flowchart LR
    A["Getting Started"] --> B["**Animation Controls & Timelines**"]
    B --> C["SVG & Motion Paths"]
    C --> D["Staggering & Synchronization"]
    D --> E["Complex Web Animations"]
    style B fill:#f97316,stroke:#c2410c,color:#fff
    style A fill:#e5e7eb,stroke:#9ca3af,color:#374151
    style E fill:#22c55e,stroke:#16a34a,color:#fff
  

Animation Instance

Every anime() call returns an animation instance — an object with properties and methods you can use to control playback.

const anim = anime({
  targets: '.box',
  translateX: 300,
  duration: 2000,
  autoplay: false   // Don't start automatically
})

The autoplay: false option is crucial when you want to control when the animation starts. Without it, the animation begins immediately and might be finished before you click your “Play” button.

.play() — Start or Resume

anim.play()

Calling .play() on a paused animation resumes from where it paused. On a stopped animation, it starts from the beginning.

.pause() — Stop Mid-Animation

anim.pause()

The animation freezes at its current position. Calling .play() resumes from there.

.reverse() — Flip Direction

anim.reverse()  // Flips the direction flag
anim.play()     // Plays in the new direction

Important: .reverse() only flips the internal direction flag — it doesn’t start playing. You must call .play() to begin reversed playback. If the animation was playing forward, after .reverse() it immediately moves backward from the current position.

.seek() — Jump to a Specific Time

anim.seek(500)         // Jump to 500ms into the animation
anim.seek(anim.duration) // Jump to the very end

seek lets you jump to any point in the animation timeline. The animation instance remembers the seeked position — calling .play() from there continues forward.

.restart() — Start Over

anim.restart()

Resets the animation to the beginning and plays it regardless of autoplay. Useful for “Replay” buttons.


The autoplay Option

By default, animations start as soon as they’re created. This is convenient for simple cases but problematic when you need manual control:

// Without autoplay: false — animation starts immediately
const anim = anime({ targets: '.box', translateX: 400, duration: 2000 })

// With autoplay: false — waits for you to call .play()
const anim = anime({ targets: '.box', translateX: 400, duration: 2000, autoplay: false })

document.querySelector('#startBtn').addEventListener('click', () => {
  anim.play()
})

Why autoplay: false matters: If you build a UI with Play/Pause/Reverse buttons and the animation starts on page load, the user might miss it entirely. Control animations should only play when the user interacts.


The finished Promise

Every animation instance has a .finished property that returns a Promise. This promise resolves when the animation completes (including all loops):

const anim = anime({
  targets: '.box',
  translateX: 300,
  duration: 1000,
  loop: 2
})

anim.finished.then(() => {
  console.log('Animation done!')
  // Start the next animation
  anime({ targets: '.box', rotate: 360 })
})

What the promise resolves after:

  • loop: 2 — after both loops finish
  • loop: true (infinite) — never resolves! Use complete callback instead
  • No loop — after the single playback

Callbacks

Callbacks are functions that fire at specific moments during an animation:

anime({
  targets: '.box',
  translateX: 300,
  duration: 2000,
  loop: 2,
  begin: function(anim) {
    console.log('Animation started at', anim.currentTime)
  },
  update: function(anim) {
    console.log('Current progress:', anim.progress + '%')
  },
  complete: function(anim) {
    console.log('All loops finished')
  },
  loopComplete: function(anim) {
    console.log('One loop iteration done')
  }
})
CallbackWhen it fires
beginFirst frame of the animation (after any delay)
updateEvery single frame (60 times per second)
completeAfter all loops have finished
loopCompleteAfter each individual loop iteration

When to use each:

  • begin — Start a loading spinner, disable a button during animation
  • update — Update a progress bar, sync other elements
  • complete — Chain the next animation, re-enable controls
  • loopComplete — Count loop iterations, alternate colors each cycle

anime.timeline() — Sequencing Animations

Timelines let you sequence multiple animations with millisecond precision:

const tl = anime.timeline({
  duration: 1000,
  easing: 'easeOutExpo'
})

tl.add({ targets: '.box1', translateX: 200 })        // Starts at 0ms
  .add({ targets: '.box2', translateX: 200 }, 500)    // Starts at 500ms
  .add({ targets: '.box3', translateX: 200 }, '+=300') // 300ms after box2 ends

How offsets work:

OffsetWhat it means
500Absolute: start at 500ms on the timeline
'+=200'Relative: start 200ms after the previous animation ends
'-=100'Overlap: start 100ms before the previous animation ends
'*=2'Multiplier: start after 2× the previous animation’s duration

Timeline Defaults Cascade

Options passed to anime.timeline() apply to all children automatically:

const tl = anime.timeline({
  duration: 800,
  easing: 'easeInOutSine',
  loop: true,
  direction: 'alternate'
})

tl.add({ targets: '.box1', translateX: 150 })
  .add({ targets: '.box2', translateX: 150 })
  .add({ targets: '.box3', translateX: 150 })

All three animations share the 800ms duration, easeInOutSine easing, infinite looping, and alternate direction. You can still override per-child if needed.

Timeline Controls

Timelines expose the same control methods as individual animations:

const tl = anime.timeline({ autoplay: false })
tl.add({ targets: '.a', translateX: 100 })
  .add({ targets: '.b', translateX: 100 }, '+=200')
  .add({ targets: '.c', translateX: 100 }, '+=200')

tl.play()
tl.pause()
tl.reverse()
tl.seek(400)
tl.restart()

Promise-Based Sequencing (Alternative)

For simpler sequences, you can use Promises and async/await instead of timelines:

async function runSequence() {
  await anime({ targets: '.box', translateX: 200, duration: 800 }).finished
  await anime({ targets: '.box', rotate: 180, duration: 600 }).finished
  await anime({ targets: '.box', scale: 0.5, duration: 400 }).finished
  await anime({ targets: '.box', scale: 1, duration: 400 }).finished
  console.log('Sequence complete')
}

runSequence()

Timeline vs Promises:

  • Use timelines when you need precise overlap control (-=100), shared options across children, or complex multi-element sequences.
  • Use Promises for simple linear sequences on a single element, or when you want the readability of async/await.

Parallel Groups with Promise.all

Run multiple animations in parallel, then do something when all finish:

async function parallelGroups() {
  const group1 = anime({ targets: '.red', translateX: 200, duration: 1000 }).finished
  const group2 = anime({ targets: '.blue', translateY: 200, duration: 1000 }).finished

  await Promise.all([group1, group2])
  await anime({ targets: '.green', scale: 2, duration: 500 }).finished
}

Reading Animation State

Animation instances expose properties that tell you what’s happening:

const anim = anime({ targets: '.box', translateX: 300, autoplay: false })

anim.progress     // 0–100 (percentage complete)
anim.currentTime  // Current time in milliseconds
anim.duration     // Total duration in milliseconds
anim.playing      // Boolean — is it running right now?
anim.reversed     // Boolean — is direction reversed?
anim.paused       // Boolean — is it paused?
anim.completed    // Boolean — has it finished all loops?
anim.direction    // 'normal' or 'reverse'

These are useful for updating UI elements like progress bars or status indicators.


Common Mistakes Beginners Make

1. Not setting autoplay: false when building controls

If you create an animation at page load without autoplay: false, it may complete before the user ever clicks a button. Always use autoplay: false when building a custom control UI.

2. Calling .reverse() without .play()

anim.reverse()   // Only flips the direction flag
// anim still not playing!

.reverse() does not auto-play. You must call .play() after .reverse().

3. Using .finished with infinite loops

// This promise NEVER resolves
const anim = anime({ targets: '.box', translateX: 300, loop: true })
anim.finished.then(() => console.log('done'))  // Never fires

Use complete callback instead when loop: true.

4. Confusing absolute vs relative timeline offsets

tl.add({ targets: '.box2', translateX: 200 }, 500)     // Starts at exactly 500ms
tl.add({ targets: '.box3', translateX: 200 }, '+=200')  // Starts 200ms AFTER box2 ends

Absolute offsets (500) are fixed timeline positions. Relative offsets ('+=200') depend on the previous animation’s end time. If the previous animation is 1000ms, '+=200' starts at 1200ms.

5. Forgetting to pass shared options to the timeline

// Repetitive: each child repeats the same easing
tl.add({ targets: '.a', translateX: 100, easing: 'easeOutExpo' })
  .add({ targets: '.b', translateX: 100, easing: 'easeOutExpo' })

// Clean: shared options on timeline
const tl = anime.timeline({ easing: 'easeOutExpo' })
tl.add({ targets: '.a', translateX: 100 })
  .add({ targets: '.b', translateX: 100 })

Practice Questions

  1. What does autoplay: false do? It prevents the animation from starting immediately. You must call .play() to begin.

  2. Does .reverse() start playing the animation? No. It only flips the direction flag. You need to call .play() afterward.

  3. What’s the difference between complete and loopComplete callbacks? complete fires once after all loops finish. loopComplete fires after each individual loop iteration.

  4. Why would you use a timeline instead of nested callbacks? Timelines give you precise offset control (absolute, relative, overlap), shared defaults, and the same control methods (play, pause, seek) on the entire sequence.

  5. What happens if you call .seek() past the animation’s duration? It clamps to the valid range. The animation appears at its end state.

Challenge

Build a “product reveal” animation sequence:

  • Box 1 (image) fades in and slides up over 800ms
  • Box 2 (title) slides in from the right, 300ms after box 1
  • Box 3 (description) fades in, starting 200ms before box 2 ends
  • All three pulse gently together (scale 1→1.02) in an infinite alternate loop after the initial reveal

FAQ

Can I chain .add() indefinitely on a timeline?
Yes. Timelines support unlimited chaining. Each .add() returns the timeline, so you can chain as many as you need.
Do callbacks fire when using .seek()?
No. begin, complete, and loopComplete only fire during real playback, not during programmatic seeking.
Can I restart a completed animation?
Yes. .restart() resets progress to 0 and plays from the beginning.
What’s the difference between .pause() and seeking to the current time?
Same visual result, but .pause() maintains the “paused” state flag. Seeking just moves the playhead.
Can I nest timelines inside other timelines?
No. You can’t directly nest timelines, but you can use anime.timeline().add() and use finished promises to sequence multiple timelines.

Try It Yourself

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Anime.js Controls Demo</title>
  <style>
    body { font-family: system-ui; padding: 2rem; background: #0f0f23; color: #e0e0e0; }
    .stage { position: relative; height: 200px; background: #1a1a3e; border-radius: 12px; padding: 20px; margin-bottom: 16px; }
    .box { width: 60px; height: 60px; border-radius: 8px; position: absolute; top: 50%; margin-top: -30px; }
    #box1 { background: #6c5ce7; left: 20px; }
    #box2 { background: #fd79a8; left: 100px; }
    #box3 { background: #00cec9; left: 180px; }
    .controls { display: flex; gap: 8px; flex-wrap: wrap; }
    .controls button { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; background: #6c5ce7; color: #fff; font-size: 14px; }
    .controls button:hover { background: #5a4bd1; }
    #status { background: #26264a; padding: 8px 12px; border-radius: 6px; font-size: 13px; color: #ffd700; }
  </style>
</head>
<body>
  <div class="stage">
    <div class="box" id="box1"></div>
    <div class="box" id="box2"></div>
    <div class="box" id="box3"></div>
  </div>
  <div class="controls">
    <button onclick="tl.play()">▶ Play</button>
    <button onclick="tl.pause()">⏸ Pause</button>
    <button onclick="tl.reverse(); tl.play()">↻ Reverse</button>
    <button onclick="tl.restart()">⏮ Restart</button>
    <span id="status">Ready</span>
  </div>

  <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js"></script>
  <script>
    const tl = anime.timeline({
      duration: 1000, easing: 'easeOutExpo', autoplay: false,
      begin: () => document.getElementById('status').textContent = 'Running',
      complete: () => document.getElementById('status').textContent = 'Complete'
    })
    tl.add({ targets: '#box1', translateX: 500 })
      .add({ targets: '#box2', translateX: 500 })
      .add({ targets: '#box3', translateX: 500 })
  </script>
</body>
</html>

What to expect: Three colored boxes. “Play” starts a sequential animation (box1 moves, then box2, then box3). “Pause” freezes. “Reverse” plays backward. “Restart” resets.


What’s Next

TutorialWhat You’ll Learn
SVG & Motion PathsAnimate along paths, morph shapes, line drawing
Staggering & SynchronizationCascading effects, grid patterns, radial stagger

Related topics: JavaScript Promises, SVG basics.

What’s Next

Congratulations on completing this Animejs Controls tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro