Improvements to Web Animations API
Motion is the only animation library with a hybrid engine, meaning its capable of dynamically running animations either via requestAnimationFrame
or via the Web Animations API (WAAPI).
This allows it to animate any value, for any render target (DOM, Three.js, canvas) while also retaining the ability to run animations with hardware acceleration.
Its animate
function comes in two sizes, mini (2.5kb) and hybrid (17kb).
Both functions provide a number of improvements to the feature set and developer experience of WAAPI, in this guide we'll take a look at some.
Springs and custom easing functions
CSS and WAAPI only support in-built easing functions like "back-in"
, "ease-in-out"
etc.
Motion extends that to support any custom easing function by automatically generating a linear()
CSS easing definition in modern browsers, with a safe fallback in older browsers
Additionally, it supports spring
animations in animateStyle
by compiling the spring into a linear()
easing and computing the appropriate duration
. Whereas in the animate
function it will pre-calculate the actual keyframes for real physics-based animations.
Default value types
WAAPI always expects a unit type for various animatable values, which can be easy to forget.
Motion knows the default value type for all popular values.
.finished
Promise
As a newer part of the WAAPI spec, the animation.finished
Promise
isn't supported in every browser. Motion will polyfill it in those browsers:
Durations as seconds
In WAAPI (and a subset of other JavaScript animation libraries), durations are set as milliseconds:
During development of Framer Motion, user testing revealed that most of our audience find seconds a more approachable unit. So in Motion, durations are defined in seconds.
Persisting animation state
In a typical animation library, when an animation has finished, the element (or other animated object) is left in the animation's final state.
But when you call WAAPI's animate
function like this:
This is the result:
The animation ends in its initial state!
WAAPI has an option you can set to fix this behaviour. Called fill
, when set to "forwards"
it will persist the animation beyond its timeline.
But this is discouraged even in the official spec. fill: "forwards"
doesn't exactly change the behaviour of the animation, it's better to think of it keeping the animation active indefinitely. As WAAPI animations have a higher priority than element.style
, the only way to change the element's styles while these animations are active is with more animations!
Keeping all these useless animations around can also lead to memory leaks.
The spec offers two solutions. One, adding a Promise
handler that manually sets the final keyframe target to element.style
:
The second is to immediately set element.style
to the animation target, then animate from its current value and let the browser figure out the final keyframe itself.
Each approach has pros and cons. But a major con they both share is making the user decide. These are unintuitive fixes to an unintuitive behaviour, and whichever is chosen necessitates a wrapping library because repeating these brittle patterns is bad for readability and stability.
So instead, Motion's animate
function will actually animate to a value, leaving in its target state once the animation is complete.
Stop animations
WAAPI's animate
function returns an Animation
, which contains a cancel
method.
When cancel
is called, the animation is stopped and "removed". It's as if the animation never played at all:
Motion adds a stop
method. This cancels the animation but also leaves the element in its current state:
Partial/inferred keyframes
In early versions of the WAAPI spec, two or more keyframes must be defined:
However, it was later changed to allow one keyframe. The browser will infer the initial keyframe based on the current visual state of the element.
Some legacy browsers, including the common WAAPI polyfills, only support the old syntax. Which means if you try and use WAAPI as currently documented, it will throw an error in many older browsers.
Motion's animate
function automatically detects these browsers and will generate an initial keyframe from window.getComputedStyle(element)
where necessary.
Interrupting animations
WAAPI has no concept of "interrupting" existing animations. So if one animation starts while another is already playing on a specific value, the new animation simply "overrides" the existing animation.
If the old animation is still running when the new one finishes, the animating value will appear to "jump" back to the old animation.
Motion automatically interrupts the animation of any values passed to animate
and animates on to the new target:
Cubic bezier definitions
In WAAPI, cubic bezier easing is defined as a CSS string:
This kind of definition will work in Motion, but we also allow this shorthand array syntax:
Independent transforms (animate
-only)
Because CSS doesn't offer styles for x
, scaleX
etc, you can't animate these properties with WAAPI. Instead, you have to animate the full transform
string:
This isn't just a matter of developer aesthetics. It means it's literally impossible to animate these properties with separate animations, or with different animation options.
Some modern browsers allow translate
, scale
and rotate
to be defined and animated separately, but even then you can't animate the axis of each.
Motion still allows the animation of transform
, but adds the ability to animate all transforms individually, for all axes:
Which means you can also animate them with different options: