How to Create Complex Timeline Animations with Precise Control Using Anime.js
Anime.js provides a specialized Timeline class that extends Timer to sequence tweens, timers, and nested timelines with millisecond-precision positioning through absolute values, relative offsets, and labeled markers.
Anime.js is a lightweight JavaScript animation engine that excels at orchestrating complex timeline animations with frame-level accuracy. The library's Timeline class, defined in src/timeline/timeline.js, serves as the foundation for sequencing multiple animation phases while maintaining precise control over start times, overlaps, and staggering effects according to the juliangarnier/anime source code.
Understanding the Timeline Core Architecture
The Timeline class extends Timer (from src/timer/timer.js) and functions as a specialized container that tracks children's offsets, total duration, labels, and looping state. When you invoke the createTimeline factory function (exported in src/index.js), it returns a fully initialized Timeline instance ready for method chaining.
The constructor initializes critical timing properties including iterationDuration, duration, and playback easing defaults. As children are added, the private helper addTlChild instantiates JSAnimation or Timer objects and triggers getTimelineTotalDuration to automatically recalculate the timeline's length without manual bookkeeping.
Adding Children with Timeline.add
The primary method for building sequences is Timeline.add, located at line 79 of src/timeline/timeline.js. This method accepts three distinct overloads:
- Animation configuration – Accepts CSS selectors or DOM nodes with animation parameters
- Timer or Timeline instances – Allows nesting of complex subsequences
- Stagger functions – Applies per-target offset calculations for distributed start times
When the third argument is a function, Anime.js treats it as a stagger callback. The timeline stores its current duration, iterates over parsed targets, and creates child tweens with positions computed by parseTimelinePosition. This architecture ensures that adding new children automatically adjusts the parent timeline's total duration.
Achieving Precise Position Control
Precise positioning distinguishes professional animation sequences from basic chained effects. The parser in src/timeline/position.js (specifically parseTimelinePosition at line 56) resolves three positioning strategies:
Absolute positioning specifies exact millisecond values relative to the timeline start. Relative expressions use string syntax like '+=200' (200ms after the previous child ends) or '<-50' (50ms before the previous child starts). Label-based positioning references named markers inserted via the .label() method, supporting compound offsets like 'pulseStart+=100'.
The parser maintains a registry of label positions and handles "previous-child" shortcuts ('<'), enabling surgical control over overlap and sequencing without hardcoding millisecond values throughout your codebase.
Staggering Animations with Per-Target Offsets
For complex timeline animations involving multiple elements, the stagger utility in src/utils/stagger.js generates position functions that calculate individual offsets based on element index, grid position, or custom ordering algorithms.
When passed as the third argument to Timeline.add, the timeline iterates over every target, invokes the stagger function to determine each element's offset, and inserts children at those computed positions via parseTimelinePosition. Stagger supports advanced features including grid-based delays, axis selection, custom easing curves, and spring physics, allowing you to create cascading effects that maintain precise temporal relationships within the broader timeline context.
Nesting Timelines and Syncing External Animations
Production-grade animations often require composing multiple independent sequences. The Timeline class supports nesting through the .add() method—you can pass another Timeline instance as a child, which then respects the parent timeline's playback controls and positioning system.
For integration with external systems, Timeline.sync (line 56 of src/timeline/timeline.js) attaches Web Animations API objects or custom callbacks to the timeline's playback position. This synchronization ensures that native WAAPI animations or canvas renderers remain frame-locked to the Anime.js timeline state.
Practical Implementation Examples
This example demonstrates staggered entrances, labeled sections, relative positioning, and callback integration:
import { createTimeline, stagger, utils } from 'animejs';
// A simple timeline with three distinct phases
const tl = createTimeline({ loop: false })
// Phase 1 – staggered entrance of boxes
.add('div.box', {
opacity: [0, 1],
translateY: [20, 0],
duration: 800,
easing: 'easeOutQuad',
}, stagger(150)) // stagger creates a 150ms offset per element
// Phase 2 – label to mark the start of the "pulse" section
.label('pulseStart')
// Phase 3 – pulse animation using a relative position
.add('div.box', {
scale: [1, 1.2, 1],
duration: 600,
easing: 'easeInOutSine',
}, '+=200') // start 200ms after the previous child finishes
// Phase 4 – a callback executed at a custom label
.add(() => console.log('All done!'), 'pulseStart+=600'); // runs 600ms after the label
// Start the timeline
tl.play();
For advanced compositions involving nested timelines and external animation synchronization:
import { createTimeline, sync } from 'animejs';
import { animate } from 'animejs';
// Create a sub-timeline that animates a logo
const logoTl = createTimeline()
.add('#logo', { rotate: 360, duration: 2000, easing: 'linear' });
// Main timeline that runs the logo animation and syncs a native WAAPI animation
const main = createTimeline({ loop: true })
.add(logoTl) // nesting
.sync(animate('#title', { // sync external animation
opacity: [0, 1],
duration: 500,
}), '+=100'); // start 100ms after logo starts
main.play();
Summary
- The Timeline class in
src/timeline/timeline.jsextends Timer and manages children throughaddTlChildand automatic duration recalculation viagetTimelineTotalDuration. Timeline.addaccepts animations, timers, or stagger functions, withparseTimelinePositioninsrc/timeline/position.jshandling absolute values, relative offsets (+=,-=), and label references.staggerinsrc/utils/stagger.jsgenerates per-target offset functions that integrate seamlessly with timeline positioning for complex sequential effects.- Nesting and
Timeline.syncenable composition of sub-timelines and synchronization with external Web Animations API objects or custom render loops. createTimelineprovides a factory method for instantiating chainable timeline instances with full playback control.
Frequently Asked Questions
How do I overlap animations instead of sequencing them sequentially?
Use relative positioning strings in the third parameter of .add(). The syntax '-=200' starts the new animation 200ms before the previous child ends, while '+=0' starts it immediately at the same time as the previous animation begins. The parseTimelinePosition function in src/timeline/position.js handles these calculations automatically.
Can I jump to a specific point in a complex timeline animation?
Yes. Because Timeline extends the base Timer class from src/timer/timer.js, you can call .seek(time) with a millisecond value or label string to jump to any position. The timeline updates all children to their appropriate states based on the requested global time, including handling nested timelines and synchronized external animations.
What is the difference between stagger and absolute positioning when animating multiple elements?
Stagger (from src/utils/stagger.js) generates a function that calculates unique start offsets for each target based on index, grid position, or easing curves. Absolute positioning places every target at the same temporal location. When you pass a stagger function to Timeline.add, the engine iterates through targets individually, computes each offset via parseTimelinePosition, and inserts children at those distributed positions, creating cascading effects without manual iteration.
How do I synchronize non-Anime.js animations with my timeline?
Use the Timeline.sync method, which accepts any object implementing the Web Animations API interface or a custom object with currentTime properties. The timeline will drive the external animation's playback position, ensuring it stays frame-locked during seeks, reverses, or speed changes. This is implemented in src/timeline/timeline.js and allows integration with CSS animations, canvas renderers, or third-party libraries.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →