Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 10.5Intermediate ~18 min · shot 5 of 6

auto-animate in One Line, and Its Limits

In one line

autoAnimate(parent) watches a parent's children and animates adds, removes and moves with FLIP, in one line and about 3.3 kB gzipped. It is the right tool for simple lists, and you should know what it does not give you.

0/6 · 0%

Think of it like this

A stagehand who watches the stage and, whenever a prop is added, removed or moved, slides it smoothly without being told how. You never write the choreography, which is wonderful until you want a specific one. Then you have to take the job back.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

MutationObserver
A browser object that calls your function when the page's DOM changes, for example when a child is added to a list.
ResizeObserver
A browser object that calls your function when an element's size changes.
controller
The object autoAnimate() returns, with enable, disable, isEnabled and destroy.
plugin
A function you pass instead of options, which returns your own keyframes for add, remove and remain.

Step by step

01Run the one-line list

Run "One line of code". The console says enabled: true | parent position: relative: the library set the parent's position for you.

02Add an item

Pause and press Add. The new li has a track [opacity, transform] of 375 ms with ease-in, and every other item has a 250 ms transform track. The list itself has a red [height, transform] track too: the parent grows by animating its height.

03Remove an item

Press Remove first. The removed item is position: absolute while it fades (250 ms, ease-out), and a red ul track [height, transform] appears: the parent shrinks by animating its height.

04Options and the switch

In "Options, the switch and reduced motion" the duration is 600 ms. Press Turn off, then Remove first: the list changes at once, document.getAnimations() is empty. Turn on Reduced motion and Run: the console says enabled: false.

Try it yourself

  1. 1

    Read the defaults

    Pause, press Add in the first example and click the new item's track: opacity and transform, 375 ms, ease-in. Click another item's track: transform, 250 ms, ease-in-out.

  2. 2

    Run as a reduced-motion user

    Switch on Reduced motion in the timeline, run the second example and press Add: the console shows reduced motion asked: true | enabled: false and no animation plays.

  3. 3

    Override it

    Add disrespectUserMotionPreference: true to the options object, switch on Reduced motion, and run: the console says enabled: true. This is a way to ignore a visitor's setting, so you should only use it for motion that carries meaning.

Code & diagrams

One line of code

Add, remove and reverse. Pause and read the tracks, especially the red one on the list itself.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

enabled: true | parent position: relative
Options, the switch and reduced motion

A 600 ms duration, a Turn off button and a reduced-motion check. Switch on the timeline's Reduced motion and run it again.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

reduced motion asked: false | enabled: true

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Animate a parent that is not the list

In the first example change the call to autoAnimate(document.body).

terminal
$ Press Add
── what you'll see ──
The list item appears at once: auto-animate only watches the children of the element you give it, and the `ul` is not a child of `body` that changed.

Myth vs fact

Myth

auto-animate respects the user's reduced-motion setting for as long as the page is open.

Fact

It checks once, when you call it. A visitor who changes the setting afterwards, or uses the site's toggle, is not noticed unless you call disable() yourself.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The library animates width and height when an element changes size, which are layout properties. Even a plain list gets one layout-cost track, the parent's height, on every add or remove (measured above), and cards that resize animate on the main thread too.

  • ▸

    Elements outside the viewport are skipped (isOffscreen), so a very long list costs less than it looks. The same idea is in our flip(): read everything, animate only what you can see.

  • ▸

    In React, useAutoAnimate from @formkit/auto-animate/react returns a [ref, enable] pair and cleans up on unmount.

Remember this

  1. 1

    The problem this solves: the FLIP code of Topics 10.1 and 10.2 is about 60 lines you must write, test and keep correct. For a todo list or a menu you often want the effect and no code. @formkit/auto-animate (version 0.10.0 here, MIT licence) gives it in one line: autoAnimate(listElement).

  2. 2

    How it works inside. It puts a MutationObserver on the parent's child list. When a child is added, removed or reordered, it reads each child's box (First, which it stored after the last change) and the new box (Last), and plays a WAAPI animation: a FLIP. It also uses a ResizeObserver and an IntersectionObserver to keep the stored boxes current.

  3. 3

    What each change gets by default (read from the library's source and from the timeline). Elements that stay: translate from their old place, 250 ms ease-in-out. A new element: scale(0.98) and opacity 0 held for the first half, then to 1, over 375 ms (1.5 times the duration) with ease-in. A removed element: put back in the parent as position: absolute at its old place, faded to 0 with scale(0.98) over 250 ms with ease-out, then removed. It sets position: relative on the parent if that was static.

  4. 4

    What it costs. When an element's size changes, remain animates width and height as well, and when something is added or removed the parent's height animates. The timeline shows the parent's track as red (layout). It also skips elements that are off-screen.

  5. 5

    Reduced motion is checked once. When autoAnimate() is called it asks matchMedia("(prefers-reduced-motion: reduce)"). If the answer is yes, it does nothing: no observer, and controller.isEnabled() is false. It does not follow a later change of the setting, and it knows nothing about Phase 7's site toggle. Call it only when motionAllowed() is true, and call controller.disable() when that changes. Pass disrespectUserMotionPreference: true only for a motion that is essential.

  6. 6

    The controller. autoAnimate() returns { parent, enable, disable, isEnabled, destroy }. disable() cancels animations in flight. destroy() disconnects every observer: call it when the list leaves the page, or the observers stay alive.

  7. 7

    Its limits. It works on direct children of one parent. It cannot move an element between two parents, share an element between two views (that is View Transitions, Phase 11, or a layout animation in Motion, Phase 15), pick a different exit per item, or run a timeline. You can pass a plugin function instead of options to write your own keyframes. When you reach those limits, the hand-written flip() is the next step.

Explain it without notes

01

How does auto-animate work, and which three kinds of change does it animate?

02

Name two limits of auto-animate and what you would use instead.

Practice

01

Write a mountList(el) that turns auto-animate on only when motion is allowed and turns it off if that changes.

02

How would you make auto-animate's duration 400 ms with ease-out?

Trade-offs

  • ↔

    One line against control. auto-animate is a good default for simple lists and a poor fit for choreography. Our own flip() is 60 lines and does exactly what the page needs.

Done when you can

  • You can say what auto-animate animates and how

  • You can read its default durations and easings

  • You can handle reduced motion and cleanup

  • You can name its limits