v4.4.0

ekwek1/sopranov4.4.0Apr 29, 2026by juliangarnier

AI Summary

A major release introducing animation enhancements and breaking changes to transform and function-based value handling.

Key Highlights

  • Fixed transform render order (perspective > translate > rotate > scale)
  • New `scrambleText` function-based value
  • New `priority` parameter for engine tick loop
  • Grid auto-layout mode

Breaking Changes

  • Fixed transform render order regardless of definition order
  • `matrix` and `matrix3d` can no longer be animated directly
  • Function-based value third argument changed from `total` to `targets`
  • Stagger callback signature changed to match function-based values

New Features

  • `scrambleText()` for character-by-character scramble/reveal
  • CSS shorthand grouping for adjacent axis properties
  • `createLayout()` accepts an `id` parameter
  • Exports `globals` from main module entry point
  • DevTools integration removed

Full Release Notes

## Breaking Changes

- **Transforms**
  - Transforms now follow a fixed render order (`perspective` > `translate` > `rotate` > `scale` > `skew`), regardless of the order they are defined in animation parameters:
    ```js
    // Before: scale rendered first because it was defined first
    animate(el, { scale: 2, translateX: 100 }) // → scale(2) translateX(100px)
    // After: order is always fixed
    animate(el, { scale: 2, translateX: 100 }) // → translate(100px, 0px) scale(2)
    ```
  - `matrix` and `matrix3d` can no longer be animated directly (they are still preserved when read from inline styles and can be set using transform: 'matrix(...)')
- **Function-based values**
  - The third argument of function-based value callbacks changed from `total` (Number) to `targets` (Array). To migrate, replace `total` with `targets.length`:
    ```js
    // Before
    translateX: (target, index, total) => total * 10
    // After
    translateX: (target, index, targets) => targets.length * 10
    ```
  - A fourth argument `prevTween` is now passed to function-based value callbacks, providing access to the previous sibling tween's computed end value for the same target and property.
- **Stagger**
  - The stagger callback signature changed to match function-based values: the third argument is now the `targets` array instead of the total count:
    ```js
    // Before
    delay: stagger(100, { use: (target, i, total) => total - i })
    // After
    delay: stagger(100, { use: (target, i, targets) => targets.length - i })
    ```

## New Features

- **scrambleText**
  - New `scrambleText()` function-based value to animate text with a character-by-character scramble/reveal effect (learn more: https://animejs.com/documentation/text/scrambletext)
- **Stagger**
  - New `grid: true` auto-grid mode that computes a 2D layout from element positions or JS object coordinates (`{x, y}`) instead of requiring explicit `[columns, rows]` dimensions
  - The `from` parameter now accepts `[x, y]` normalized coordinate arrays (0 to 1) for precise grid origin control
- **Timer / Animation / Timeline**
  - New `priority` parameter to control execution order within the engine tick loop
- **Transforms**
  - Adjacent axis properties are now grouped into CSS shorthand for shorter transform strings:
    - `translateX` + `translateY` → `translate(x, y)`
    - `translateX` + `translateY` + `translateZ` → `translate3d(x, y, z)`
    - `scaleX` + `scaleY` → `scale(x, y)`
    - `scaleX` + `scaleY` + `scaleZ` → `scale3d(x, y, z)`
  - Inline transform parsing rewritten to properly handle multi-argument CSS functions (`translate(x, y)`, `scale3d(x, y, z)`) and nested `calc()` expressions
- **Layout**
  - `createLayout()` now accepts an `id` parameter
- **Exports**
  - `globals` is now exported from the main module entry point
  - `forEachChildren`, `addChild`, `removeChild` helpers are now exported from `utils`

## Bug Fixes and improvements

- **JS Animation**
  - `revert()` now restores object properties and DOM attributes to their original pre-animation values, in addition to CSS styles and transforms (previously only CSS styles and transforms were reverted, leaving animated object props and attributes at their last animated value). The public `cleanInlineStyles()` utility keeps its original behavior and only clears inline styles and transforms.
  - Fix `getOriginalAnimatableValue` not storing the original inline value for object properties and DOM attributes, preventing proper revert
- **morphTo**
  - Fix chaining in keyframe arrays and timelines producing incorrect intermediate shapes
- **Chainable utils**
  - Fix accessing non-existent chainable methods throwing an error instead of returning `undefined`
- **splitText**
  - Fix `addEffect()` returning `undefined` instead of `this` when the effect argument is not a function
- **Layout**
  - Fix scroll-controlled layout animations not properly calling `onComplete` and `onPause` callbacks
- **keepTime**
  - Fix `_startTime` not being preserved when reconstructing tracked animations, causing timing jumps on effect refresh

## Changes

- **Engine**
  - DevTools integration (`AnimeJSDevTools`) removed
  - Render loop performance: inline closures in `tick()` and `render()` extracted to module-scope functions to avoid allocation every frame
  - Tween composition is now resolved before computing `to`/`from`/`duration`/`delay` values, so the previous sibling tween is available to function-based value callbacks
- **splitText**
  - Type annotation for `splitText()` and `TextSplitter` target parameter widened from `HTMLElement` to `Element`
- **Tween value chaining**
  - Each tween now pre-computes and stores its end value at construction time
  - Enables function-based values like `morphTo` and `scrambleText` to read the previous tween's end value when used in keyframe arrays or timelines
- **morphTo**
  - Now properly chains in keyframe arrays and timelines by reading the previous tween's computed end value instead of mutating the target element with a Symbol
- **Editor integration (👀)**
  - New `globals.editor` hook system allowing an external editor to intercept `animate()` and `createTimeline()` calls