I recently discovered that react-transition-group
does not support FLIP list animations (in contrast, VUE’s built-in TransitionGroup
does).
So how should we implement a beautiful list animation in the world of React? This article will explain my exploration process.
Framer Motion#
framer-motion
is an excellent animation library, its API design is very elegant, it can be said to be the best in React animation libraries,
and it is currently the most popular React animation library.
FLIP transitions in framer-motion
are supported by the positionTransition
property.
The framer-motion
official provides a Notifications example, from which we can see how to use positionTransition
to implement list animations.
The core code is as follows:
import React, { useState } from 'react'
import { AnimatePresence, motion } from 'framer-motion'
import { CloseButton, remove, add } from './utils'
export default function App() {
const [notifications, setNotifications] = useState([0])
return (
<div className="container">
<ul>
<AnimatePresence initial={false}>
{notifications.map(id => (
<motion.li
key={id}
positionTransition // <--- positionTransition
initial={{ opacity: 0, y: 50, scale: 0.3 }}
animate={{ opacity: 1, y: 0, scale: 1 }}
exit={{ opacity: 0, scale: 0.5, transition: { duration: 0.2 } }}
>
<CloseButton
close={() => setNotifications(remove(notifications, id))}
/>
</motion.li>
))}
</AnimatePresence>
</ul>
<button
className="add"
onClick={() => setNotifications(add(notifications))}
>
+
</button>
</div>
)
}
After specifying positionTransition
, framer-motion
will automatically calculate the position of the current element when it changes,
and then implement the animation through transform
and opacity
.
View Transition#
But sometimes we don’t want to use framer-motion
because it’s quite large in size.
With the view transition api
, we can easily implement list animations without introducing external libraries.
Simply put, the view transition api
can automatically implement transitions for changes in elements with the same view-transition-name
in document.startViewTransition
.
We can start to modify the framer-motion
example, replace motion.li
with a regular li
, and specify view-transition-name
.
// ...
<ul>
{notifications.map(id => (
<li
key={id}
// In css, view-transition-name applies this variable
style={{ "--transition-name": `tn-${ id } ` }}
id={`li-${ id }`}
>
<CloseButton
close={() => setNotifications(remove(notifications, id))}
/>
</motion.li>
))}
</ul>
Next, since the view transition api
requires all changes to be completed in the callback of document.startViewTransition
,
we need to wrap the state changes in ReactDom’s flushSync
:
import { flushSync } from 'react-dom'
// Close button
<CloseButton
close={() => {
document.startViewTransition(() =>
flushSync(() => setNotifications(remove(notifications, id)))
);
}}
/>
// Add button
<button
className="add"
onClick={() => {
document.startViewTransition(() => {
flushSync(() => {
setNotifications(add(notifications));
});
});
}}
>
+
</button>
At this point, you already have a list animation that is no worse than framer-motion
.
In the framer-motion
example, there is also a scaling animation when the card exits. Currently, our example fades out by default when the card exits.
We can set a new view-transition-name
for the element that is about to exit when exiting, and then specify a scaling animation for this view-transition-name
in css.
<CloseButton
close={() => {
const li = document.getElementById(`li-${ id }`);
li.style['view-transition-name'] = "tn-out";
document.startViewTransition(() =>
flushSync(() => setNotifications(remove(notifications, id)))
);
}}
/>
// :only-child represents that there is only view-transition-old and no view-transition-new
::view-transition-old(tn-out):only-child {
animation: scale-out .3s forwards;
}
We can also set an entrance animation for the card:
li {
view-transition-name: var(--transition-name);
animation: scale-in .3s;
}
@keyframes scale-in {
from {
opacity: 0;
transform: translateY(50px) scale(0.3);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
The effect is as follows:
Conclusion#
The view transition api
is a very excellent API, it allows us to implement beautiful list animations without introducing external libraries.
But currently, it has some compatibility issues (chrome 111+), so be careful when using it in actual projects.
The effects it can achieve far exceed the examples in this article, and I will bring more applications of view transition api
in the future.