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

> Discover how Anime.js expertly manages percentage-based keyframe animations by converting them into uniform tweenable segments for seamless playback.

- Repository: [Julian Garnier/anime](https://github.com/juliangarnier/anime)
- Tags: internals
- Published: 2026-03-04

---

**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`](https://github.com/juliangarnier/anime/blob/main/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:

```javascript
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](https://github.com/juliangarnier/anime/blob/master/src/animation/animation.js#L27-L33)).

### 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:

```javascript
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](https://github.com/juliangarnier/anime/blob/master/src/animation/animation.js#L152-L157)).

### 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:

```javascript
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](https://github.com/juliangarnier/anime/blob/master/src/animation/animation.js#L164-L179)).

### Propagating Easing Values

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

```javascript
// 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](https://github.com/juliangarnier/anime/blob/master/src/animation/animation.js#L185-L196)).

## Constructor Integration and Parameter Merging

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

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

```

This merge operation (lines [37‑41](https://github.com/juliangarnier/anime/blob/master/src/animation/animation.js#L37-L41)) ensures that the final parameter object contains the expanded, duration-based keyframe arrays required by the composition engine in [`src/animation/composition.js`](https://github.com/juliangarnier/anime/blob/main/src/animation/composition.js).

## Practical Implementation Examples

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

```javascript
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:

```javascript
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`](https://github.com/juliangarnier/anime/blob/main/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.