How Anime.js Handles Keyframe Animations with Percentage-Based Timing

Anime.js converts percentage-based keyframes (e.g., "25%", "100%") into absolute duration segments internally, normalizing them into a standard tweenable array that the engine processes uniformly.

The juliangarnier/anime library allows developers to define animation sequences using CSS-like percentage strings instead of explicit durations. This percentage-based syntax is transformed into time-based keyframes inside src/animation/animation.js, enabling the runtime to reuse its existing tween engine regardless of how the keyframes were originally defined.

The Internal Conversion Pipeline

When you pass an object with percentage keys to the keyframes parameter, Anime.js triggers a normalization routine inside the generateKeyframes helper function. This process decomposes the percentage map into discrete property animations with calculated durations.

Detecting Object-Style Keyframes

The constructor first determines whether the supplied keyframes use percentage syntax by checking if the parameter is not an array. When an object is detected, the code retrieves the total animation duration from the parameters or defaults:

else {
  const totalDuration = /** @type {Number} */(setValue(parameters.duration, globals.defaults.duration));

This totalDuration serves as the baseline for all subsequent percentage calculations (see lines 27‑33).

Parsing Percentage Offsets

The generateKeyframes function extracts all keys from the keyframes object, converts the percentage strings into decimal offsets, and sorts them in ascending chronological order:

const keys = Object.keys(keyframes)
  .map(key => ({ o: parseFloat(key) / 100, p: keyframes[key] }))
  .sort((a, b) => a.o - b.o);

Each key becomes an object containing:

  • o: The normalized offset (0.0 to 1.0)
  • p: The property definitions for that keyframe

This sorting step ensures that subsequent calculations process keyframes in temporal sequence regardless of the order they were defined in the source object (see lines 152‑157).

Calculating Per-Property Durations

For every property found across all keyframes, Anime.js builds a dedicated array where each entry represents a tween segment. The duration of each segment is derived from the difference between the current offset's absolute time and the cumulative time already allocated to that property:

const duration = offset * totalDuration;
…
keyObj.duration = duration - (length ? durProgress : 0);

Here, offset * totalDuration converts the percentage (e.g., 0.25) into milliseconds (e.g., 500 for a 2000ms animation). The subtraction ensures that each segment lasts exactly the time between its start offset and the previous keyframe's offset (see lines 164‑179).

Propagating Easing Values

To maintain smooth motion across segment boundaries, the engine carries forward easing functions when a keyframe omits them:

// Pseudocode representation of lines 185-196
if (currentKeyframe.ease) {
  activeEase = currentKeyframe.ease;
} else {
  currentKeyframe.ease = activeEase; // Carry forward from previous
}

This propagation prevents abrupt jumps in animation velocity when transitioning between percentage-defined stops (see lines 185‑196).

Constructor Integration and Parameter Merging

After transformation, the normalized keyframes are merged back into the animation parameters inside the Animation constructor:

const kfParams = /** @type {AnimationParams} */(parameters).keyframes;
const params = /** @type {AnimationParams} */(
  kfParams ? mergeObjects(generateKeyframes(kfParams, parameters), parameters) : parameters
);

This merge operation (lines 37‑41) ensures that the final parameter object contains the expanded, duration-based keyframe arrays required by the composition engine in src/animation/composition.js.

Practical Implementation Examples

The following demonstrates percentage-based keyframes that move an element in a square pattern:

anime({
  targets: '#box',
  duration: 2000,
  easing: 'linear',
  keyframes: {
    '0%':   { translateX: 0,   translateY: 0 },
    '25%':  { translateX: 150 },
    '50%':  { translateY: 150 },
    '75%':  { translateX: 0 },
    '100%': { translateX: 0, translateY: 0 }
  }
});

Under the hood, Anime.js processes this as:

  • 0% → 0ms start time
  • 25% → 500ms duration (25% of 2000ms)
  • 50% → 500ms duration (difference between 50% and 25%)
  • 75% → 500ms duration
  • 100% → 500ms duration

The equivalent array-based definition would be:

anime({
  targets: '#box',
  duration: 2000,
  easing: 'linear',
  keyframes: [
    { translateX: 0, translateY: 0, duration: 500 },
    { translateX: 150, duration: 500 },
    { translateY: 150, duration: 500 },
    { translateX: 0, duration: 500 },
    { translateX: 0, translateY: 0, duration: 500 }
  ]
});

Both produce identical motion, but the percentage syntax aligns with CSS animation conventions and simplifies timeline visualization.

Summary

  • Percentage strings (e.g., "33.33%") are parsed into decimal offsets and multiplied by the total animation duration to generate absolute millisecond timestamps.
  • The generateKeyframes function in src/animation/animation.js handles the conversion, sorting offsets, and calculating per-segment durations by subtracting cumulative progress from each offset-based time.
  • Easing continuity is preserved through automatic propagation: if a keyframe lacks an easing definition, it inherits the previous segment's easing function.
  • The constructor merges transformed keyframes back into the parameters object, allowing the composition engine to treat percentage-based and array-based keyframes identically.

Frequently Asked Questions

What percentage formats does Anime.js support?

Anime.js accepts any numeric string followed by a percent sign, including decimal values like "12.5%" or "33.33%". The parseFloat conversion in generateKeyframes strips the % character and divides by 100, so "50%" becomes 0.5 and "7.5%" becomes 0.075.

How does Anime.js calculate segment durations from percentages?

The library multiplies each percentage offset by the total animation duration to get an absolute time, then subtracts the cumulative duration already assigned to that property. For a 1000ms animation with keyframes at "0%", "30%", and "100%", the segments are calculated as 0ms, 300ms, and 700ms respectively.

Can percentage keyframes mix with regular array keyframes?

No, individual animations must use one format or the other for the keyframes property. However, Anime.js normalizes both formats into the same internal structure, so you can achieve equivalent results by converting percentage objects to duration arrays manually if needed.

Does easing persist across percentage-defined segments?

Yes. According to the source code in lines 185‑196, when a percentage keyframe omits an easing value, the engine automatically applies the previous keyframe's easing function. This ensures smooth transitions between stops unless you explicitly define a different easing function for a specific percentage point.

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 →