How to Handle Animation Callbacks and Events in Anime.js: A Complete Guide

Anime.js exposes a rich callback system through onBegin, onUpdate, onComplete, and scroll-specific handlers that let you hook into every phase of an animation's lifecycle, all wired into the core rendering loop in src/core/render.js.

The juliangarnier/anime library provides granular control over animation workflows through its comprehensive animation callbacks and events system. Whether you need to trigger logic when an animation begins, monitor progress during each frame, or respond to scroll position changes, the engine's architecture ensures your functions execute at precisely the right moment. This guide examines the implementation details from the source code to help you leverage these hooks effectively.

Core Animation Callbacks

The seven primary lifecycle callbacks are defined in src/core/globals.js and invoked by the rendering engine in src/core/render.js. Each callback receives the tickable instance (the animation object) as its sole argument, allowing real-time introspection of properties like _currentTime, progress, and completed.

The Seven Lifecycle Callbacks

Callback Invocation Timing Implementation Location
onBegin When current time becomes > 0 for the first time src/core/render.js lines 107-110
onBeforeUpdate Before tweens are evaluated for the current frame src/core/render.js lines 136-138
onUpdate After tweens have been rendered src/core/render.js lines 285-287
onLoop When animation loops to a new iteration (leaf animations only) src/core/render.js lines 118-120
onComplete When animation reaches final state or setter finishes src/core/render.js lines 94-104 and 301-311
onRender Immediately after first render when autoplay:false or forcing tweens src/core/render.js lines 80-82
onPause / onResume When animation is paused or resumed src/timer/timer.js (Timer class)

If you omit a callback, the engine substitutes a noop function from globals.defaults, ensuring the render loop executes safely without conditional checks.

Callback Registration in the Constructor

When you invoke anime(), the JSAnimation constructor in src/animation/animation.js merges user-supplied callbacks with defaults:

// src/animation/animation.js – constructor (excerpt)
const {
  onBegin, onBeforeUpdate, onUpdate,
  onLoop, onComplete, onRender, …
} = params;               // user parameters

const animDefaults = parent ? parent.defaults : globals.defaults;

// The callbacks are attached to the instance:
this.onRender = onRender || animDefaults.onRender;

This pattern guarantees that the render loop can call tickable.onBegin(...) and other handlers without existence checks.

Timeline-Level Callbacks

A Timeline (src/timeline/timeline.js) is itself a Tickable instance. Its callbacks (onBegin, onUpdate, onComplete, onLoop) are invoked from the same render loop as leaf animations. When a timeline finishes, the engine resolves its internal Promise (_resolve), enabling await anime.timeline(...) syntax.

Scroll-Based Events and Callbacks

The ScrollObserver class in src/events/scroll.js provides event-driven callbacks based on viewport position. The handleScroll() method (around line 200) calculates isInView, updates progress, and dispatches the appropriate handlers.

Key scroll callbacks include:

  • onEnter / onEnterForward / onEnterBackward: Trigger when the target enters the viewport at the defined threshold.
  • onLeave / onLeaveForward / onLeaveBackward: Trigger when the target exits the viewport.
  • onUpdate: Fires on every scroll tick while the observer is active.
  • onSyncEnter / onSyncLeave / onSyncComplete: Helper callbacks when the observer is synced to a linked animation.

The observer computes thresholds via updateBounds(), then in handleScroll() detects entry/exit and calls the corresponding callbacks. When linked to an animation via link(), the observer drives animation.seek() based on scroll progress.

Practical Code Examples

Basic Animation with All Lifecycle Callbacks

anime({
  targets: '.dot',
  translateX: 400,
  rotate: [0, 360],
  duration: 1500,
  easing: 'easeInOutQuad',
  onBegin: anim => console.log('▶️ started', anim.id),
  onBeforeUpdate: anim => console.log('🔄 before frame', anim.currentTime),
  onUpdate: anim => console.log('📊 progress', anim.progress),
  onLoop: anim => console.log('🔁 looped', anim._currentIteration),
  onComplete: anim => console.log('✅ done', anim.id)
});

Internally, these map to tickable.onBegin(...) at src/core/render.js line 107, tickable.onBeforeUpdate(...) at line 136, tickable.onUpdate(...) at line 285, tickable.onLoop(...) at line 118, and the completion logic at lines 301-311.

Timeline with Nested Callbacks

const tl = anime.timeline({
  easing: 'easeOutExpo',
  duration: 800,
  onBegin: t => console.log('Timeline start'),
  onComplete: t => console.log('Timeline end')
});

tl
  .add({ targets: '.circle', translateY: -200 })
  .add({ targets: '.square', rotate: 180 }, '-=400')
  .add({
    targets: '.triangle',
    opacity: [0, 1],
    onBegin: t => console.log('Triangle enters')
  });

The timeline aggregates child animations while maintaining its own callback lifecycle through the Tickable interface.

Scroll-Driven Animation with Event Callbacks

import { onScroll } from 'animejs';

const anim = anime({
  targets: '.panel',
  translateY: [0, -200],
  autoplay: false,
  duration: 2000
});

onScroll({
  target: '.panel',
  container: window,
  enter: 'top 80%',
  leave: 'bottom 20%',
  onEnter: () => console.log('Entered view'),
  onLeave: () => console.log('Left view'),
  sync: true,
  link: anim
});

In src/events/scroll.js, the ScrollObserver creates a ScrollContainer, computes offsets via updateBounds(), and calls linked.seek() during handleScroll() when sync is enabled (lines 57-61).

Custom Restart Logic in onComplete

anime({
  targets: '.bounce',
  translateY: [0, -100],
  direction: 'alternate',
  loop: false,
  duration: 600,
  onComplete: function (anim) {
    console.log('Bounced once – restarting');
    anim.restart(); // Inherited from Timer class
  }
});

The restart() method resets internal timers and triggers onBegin again, demonstrating that callbacks receive fully-featured instances capable of self-manipulation.

Summary

  • Anime.js provides seven core lifecycle callbacks (onBegin, onBeforeUpdate, onUpdate, onLoop, onComplete, onRender, onPause/onResume) implemented in src/core/render.js and src/timer/timer.js.
  • Callbacks are merged with defaults in the JSAnimation constructor (src/animation/animation.js) and receive the tickable instance as their argument.
  • Timelines inherit the same callback system as individual animations and resolve a Promise upon completion.
  • ScrollObserver (src/events/scroll.js) adds viewport-based callbacks (onEnter, onLeave, onUpdate) that can synchronize animations with scroll position via the link() method.
  • All callbacks execute with zero conditional overhead in the render loop, using noop fallbacks from src/core/globals.js when not specified.

Frequently Asked Questions

What arguments do Anime.js callbacks receive?

All animation callbacks receive a single argument: the tickable instance that triggered the event. This object exposes properties like currentTime, progress, began, and completed, allowing you to inspect or manipulate the animation state in real time according to the implementation in src/animation/animation.js.

How do I run code when an animation starts playing?

Use the onBegin callback, which fires when the animation's current time becomes greater than zero for the first time. According to src/core/render.js lines 107-110, this triggers immediately after the animation transitions from a pending state to active playback.

Can I use async/await with Anime.js timelines?

Yes. The Timeline class in src/timeline/timeline.js implements a Promise-based completion system. When the timeline finishes, the engine resolves its internal _resolve function, allowing you to write await anime.timeline({...}).add(...) and pause execution until all animations complete.

What is the difference between onUpdate and onBeforeUpdate?

onBeforeUpdate fires before the engine evaluates tweens for the current frame (src/core/render.js lines 136-138), making it ideal for preprocessing or modifying target values. onUpdate fires after tweens have been rendered (lines 285-287), providing access to the final computed values for the frame.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →