React animation implementation has always been a difficult point in development. This article will introduce react-spring in an easy-to-understand manner and explore some practical application cases. While react-spring is not React's only animation library, it is one of the most popular and powerful libraries.
This article will use the latest 9.x version (release candidate version at the time of writing). If it has not been officially published at the time of reading this article, please use react-spring@next
to install it. According to my experience and the main maintainer, the code is very stable. The only problem I've had is a small error when used with concurrency mode, which can be tracked in a GitHub repository.
react-spring quick look
Before delving into practical application cases, let's take a quick look at springs, high animations, and transition animations. A runnable demonstration will be provided at the end of this section, so there is no need to worry about the confusion you may encounter during the process.
springs
Let's consider the classic "Hello world" of animation: the fading in and out of content. Let's pause and think about how to switch to show and hide without any animation. It looks like this:
export default function App() { const [showing, setShowing] = useState(false); Return ( <div> <div style="{{" opacity: showing :> This content will fade in and fade out </div> <button onclick="{()"> setShowing(val => !val)}>Toggle</button> <hr> </div> ); }
Simple, but boring. How do we animate changes in opacity? Wouldn't it be nice if we could declaratively set the desired opacity based on the state, as above, but letting these values be smoothly animated? This is what react-spring does. React-spring can be considered as our middleman, which handles our ever-changing style values, resulting in a smooth transition between the animation values we want. Like this:
const [showA, setShowA] = useState(false); const fadeStyles = useSpring({ config: { ...config.stiff }, from: { opacity: 0 }, to: { opacity: showA ? 1 : 0 } });
We use from
to specify the initial style value and specify the current value in to
section according to the current state. The return value fadeStyles
contains the actual style value we apply to the content. We just need to do the last thing...
You might think you could do this:
<div style="{fadeStyles}"> ... </div>
But this doesn't work. We cannot use normal div
, but we need to use react-spring div
created from animated
export. This may sound confusing, but it actually just means:
<animated.div style="{fadeStyles}"> ... </animated.div>
That's it.
Highly animated
Depending on what we are animating, we may want the content to slide up and down, from zero height to its full size so that the surrounding content adjusts and flows into place smoothly. You might wish we could simply copy the code above, the height changes from zero to auto, but unfortunately, you can't animate it to auto height. This works in both normal CSS and react-spring. Instead, we need to know the actual height of the content and specify it in to
section of spring.
We need to dynamically get the height of any content in order to pass its value to react-spring. It turns out that the web platform has designed something specifically for this: ResizeObserver
. And its support is actually pretty good! Since we are using React, we will of course wrap the usage in a hook. My hook looks like this:
export function useHeight({ on = true /* no value means on */ } = {} as any) { const ref = useRef<any> (); const [height, set] = useState(0); const heightRef = useRef(height); const [ro] = useState( () => new ResizeObserver(packet => { if (ref.current && heightRef.current !== ref.current.offsetHeight) { heightRef.current = ref.current.offsetHeight; set(ref.current.offsetHeight); } }) ); useLayoutEffect(() => { if (on && ref.current) { set(ref.current.offsetHeight); ro.observe(ref.current, {}); } return () => ro.disconnect(); }, [on, ref.current]); return [ref, height as any]; }</any>
We can choose to provide an on
value to toggle the enablement and disabling of the measurement function (this will come in handy later). When on
is true, we tell ResizeObserver
to observe our content. We return a ref that needs to be applied to whatever we want to measure, as well as the current height.
Let's see how it actually works.
const [heightRef, height] = useHeight(); const slideInStyles = useSpring({ config: { ...config.stiff }, from: { opacity: 0, height: 0 }, to: { opacity: showB ? 1 : 0, height: showB ? height: 0 } }); <animated.div style="{{" ...slideinstyles overflow:> <div ref="{heightRef}"> This content will fade in and fade out with sliding </div> </animated.div>
useHeight
gives us the height value of the ref to measure and the content we pass it to our spring. Then we apply ref and apply height style.
Oh, don't forget to add overflow: hidden
in the container. This allows us to correctly include the height values we adjusted.
Animation transition
Finally, let's see how to add animated items to the DOM and remove animated items from the DOM. We already know how to animate an existing project and keep the value changes of the items in the DOM, but to animate adding or removing items, we need a new hook: useTransition
.
If you've used react-spring before, this is one of the few places where the 9.x version has significant changes in its API. Let's take a look.
To animation the project list, like this:
const [list, setList] = useState([]);
We will declare our transformation function as follows:
const listTransitions = useTransition(list, { config: config.gentle, from: { opacity: 0, transform: "translate3d(-25%, 0px, 0px)" }, enter: { opacity: 1, transform: "translate3d(0%, 0px, 0px)" }, leave: { opacity: 0, height: 0, transform: "translate3d(25%, 0px, 0px)" }, keys: list.map((item, index) => index) });
As I mentioned earlier, the return value listTransitions
is a function. react-spring is tracking the list array, tracking items that have been added and deleted. We call the listTransitions
function, providing a callback function that accepts a single style object and a single item, react-spring will call it for each item in the list based on whether the item is newly added, newly deleted, or just located in the list, and use the correct style.
Note the keys section: This allows us to tell react-spring how to recognize objects in the list. In this case, I decided to tell react-spring the index of the item in the array to define the item uniquely. Normally, this is a bad idea, but now, it allows us to see how the feature actually works. In the following demonstration, the Add Project button adds the item to the end of the list when clicked, and the Delete Last Project button removes the most recently added item from the list. So if you type in the input box and quickly click the Add button and then click the Delete button, you will see the same item starts smoothly entering and then immediately leave from any stage of the animation. Instead, if you add an item and quickly hit the Delete button and Add button, the same item will start sliding, then suddenly stops in place and slide back to where it is.
Demo
Wow, I said so much! This is a runnable demo that shows everything we just covered.
[Demo link]
Other details
Have you noticed that when you slide content down in the demo, it bounces into place like…spring? This is where the name comes from: react-spring uses spring physics to interpolate our ever-changing values. It does not simply divide the value change into N equal increments and then apply these increments within N equal delays. Instead, it uses more complex algorithms to produce this spring-like effect, which will appear more natural.
The spring algorithm is fully configurable, with some presets you can use directly - the above demonstration uses stiff
and gentle
presets. Please refer to the documentation for more information.
Also note how I animate values within translate3d
values. As you can see, the syntax is not the most concise, so react-spring provides some shortcuts. There is documentation on this, but for the rest of this article, I will continue to use the full non-shortcut syntax for the sake of clarity.
Finally, I want to note that when you slide up the content in the above demo, you may see the content beneath it pounding a little at the end. This is due to the same bounce effect. It looks clear when the content bounces into place, but not when we slide the content upwards. Stay tuned for how we will turn it off. (spoiler, it's clamp
attribute).
Some notes on these sandboxes
Code Sandbox uses hot reload. When you change the code, the changes are usually reflected immediately. This is cool, but it can be destructive to the animation. If you start tinkering and then see strange, ostensibly incorrect behavior, try refreshing the sandbox.
Other sandboxes in this article will use modality. For reasons I can't figure out completely, you won't be able to modify any code when the modal is turned on - the modal refuses to give up focus. So be sure to turn off the modal before trying any changes.
Build practical applications
These are the basic building blocks of react-spring. Let's use them to build something more interesting. Given all of the above, you might think react-spring is very easy to use. Unfortunately, in practice, figuring out some of the subtleties that need to be handled correctly can be tricky. Many of these details will be explored in depth in the rest of this article.
I’ve written before is related to my booklist project in some way. This post is no exception – it's not an obsession, it's just that the project happens to have a publicly available GraphQL endpoint and a lot of existing code that can be exploited, making it an obvious goal.
Let's build a UI that allows you to open modals and search for books. When the results appear, you can add them to the run list of selected books displayed below the modal. Once done, you can turn off the modal and click a button to find books similar to the selected book.
We will start with a fully functional UI and then gradually add animations to the sections, including interactive demos.
If you really want to know what the end result looks like, or if you are already familiar with react-spring and want to see if I cover something you don't know yet, here is it (it won't win any design awards, I'm pretty clear). The rest of this article will introduce the process of achieving the final state step by step.
Animate our modals
Let's start with our modality. Before we start adding any data, let's animate our modals very well. This is what a basic, unanimated modal looks like. I'm using Ryan Florence's Reach UI (particularly modal components), but the idea is the same no matter what you use to build the modal. We want to let our background fade in and convert our modal content.
Since the modal is conditionally rendered according to some "open" attribute, we will use useTransition
hook. I've wrapped the Reach UI modal with my own modal component and rendered empty content or actual modal based on isOpen
attribute. We just need to animate it by converting hooks.
Here is what the conversion hook looks like:
const modalTransition = useTransition(!!isOpen, { config: isOpen ? { ...config.stiff } : { duration: 150 }, from: { opacity: 0, transform: `translate3d(0px, -10px, 0px)` }, enter: { opacity: 1, transform: `translate3d(0px, 0px, 0px)` }, leave: { opacity: 0, transform: `translate3d(0px, 10px, 0px)` } });
There are no big surprises here. We want to fade in and provide a slight vertical transition based on whether the modal is active or not. The weird part is this:
config: isOpen ? { ...config.stiff } : { duration: 150 },
I just want to use spring physics when the modal is turned on. The reason for this - at least in my experience - is that when you turn off the modal, the background disappears for too long, which causes the underlying UI to be interactive for too long. So when the modal is on, it will bounce in place nicely by spring physics, and when closed, it will quickly disappear in 150 milliseconds.
Of course, we will present our content via the conversion function returned by the hook. Note that I'm extracting opacity styles from the style object to apply to the background and then applying all animation styles to the actual modal content.
return modalTransition( (styles, isOpen) => isOpen && ( <animateddialogoverlay allowpinchzoom="{true}" initialfocusref="{focusRef}" isopen="{isOpen}" ondismiss="{onHide}" style="{{" opacity: styles.opacity> <animateddialogcontent style="{{" border: solid hsla borderradius: maxwidth:> <div> <div> <standardmodalheader caption="{headerCaption}" onhide="{onHide}"></standardmodalheader> {children} </div> </div> </animateddialogcontent> </animateddialogoverlay> ) );
[Demo link]
The remaining part is omitted because the length is too long and is repeated with the previous content. The core idea is to use react-spring's useSpring
and useTransition
hooks, combined with ResizeObserver
to achieve various animation effects, including fading in and out, height changes, and entry and exit animations of list items. The article explains in detail how to deal with animation details, such as initial state, animation duration, and how to avoid animation conflicts. The final result is an interactive UI with smooth and user experience.
The above is the detailed content of Making Sense of react-spring. For more information, please follow other related articles on the PHP Chinese website!

GooFonts is a side project signed by a developer-wife and a designer-husband, both of them big fans of typography. We’ve been tagging Google

Pavithra Kodmad asked people for recommendations on what they thought were some of the most timeless articles about web development that have changed their

Learning how to build GraphQL APIs can be quite challenging. But you can learn how to use GraphQL APIs in 10 minutes! And it so happens I've got the perfect

When a component lives in an environment where the data queries populating it live nearby, there is a pretty direct line between the visual component and the

Here's some legit CSS trickery from yuanchuan. There is this CSS property offset-path. Once upon a time, it was called motion-path and then it was renamed. I

Miriam Suzanne explains in a Mozilla Developer video on the subject.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

VSCode Windows 64-bit Download
A free and powerful IDE editor launched by Microsoft

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

WebStorm Mac version
Useful JavaScript development tools

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment