renature
renature
is a physics-based animation library for React inspired by the natural world. It takes inspiration from the physics of our universe and models real-world forces like gravity, friction, air resistance, and fluid dynamics.
renature
is influenced by other popular physics-based animation libraries like react-spring and elements of Framer Motion. In this article, we will learn how to create animations in renature
by building several examples and applications.
First things first: install the renature
package by running either of the commands below:
npm install --save renature # or yarn add renature
renature
provides six hooks we can use in creating animations. The first three are the useFriction
, useGravity
, and useFluidResistance
hooks. These hooks animate the properties of a single element.
They all work in a similar manner, but as their names imply, the natural forces they are modeled after and the physics they depend on will vary. Also, the config object varies from hook to hook depending on the force you’re using.
There are also the useFrictionGroup
, useGravityGroup
, and useFluidResistanceGroup
hooks, which we can use to animate the properties of a group of elements simultaneously.
Let’s take a look at a basic implementation using the useFriction
hook.
import { useFriction } from "renature"; import "./styles.css"; export default function App() { const [props] = useFriction({ from: { transform: "translateX(100px)" }, to: { transform: "translateX(300px)" }, config: { mu: 0.2, mass: 20, initialVelocity: 5 }, repeat: Infinity }); return ( <div className="App"> <div {...props} className="box" /> </div> ); }
If you’re familiar with react-spring, you’ll notice renature
has a similar declarative API. Every hook in renature
expects from
and to
properties wherein we describe the CSS states we want to animate to and from. If a value is set for a property in from
but not in to
, that property will be ignored, and it won’t be animated.
Sometimes we might want our animations to run infinitely without stopping. We can do this by applying repeat: Infinity
to our animation configuration.
Since we used the useFriction
hook, we pass in the corresponding physics parameters. As its name indicates, the useFriction
hook is modeled after the frictional force. The parameters are:
mu
, which is the coefficient of kinetic frictionmass
, which is the mass of the moving bodyinitialVelocity
, which is the initial velocity of the movementThe wonderful thing about renature
is that you don’t need to be a science guru to make awesome animations.
renature
So we’ve seen how the useFriction
hook works and how to animate the transform
property of an element. Where and when necessary, we can also animate multiple properties simultaneously:
import { useFriction } from "renature"; import "./styles.css"; export default function App() { const [props] = useFriction({ from: { transform: "translateX(100px), rotate(0deg)", background: "red", borderRadius: "0%" }, to: { transform: "translateX(300px), rotate(360deg)", background: "steelblue", borderRadius: "50%" }, config: { mu: 0.2, mass: 20, initialVelocity: 5 }, repeat: Infinity }); return ( <div className="App"> <div {...props} className="box" /> </div> ); }
To animate multiple properties of an element, add more properties of the element you want to animate.
We may want to animate multiple elements simultaneously. Grouped animations allow us to specify the number of elements to animate and to set the configuration for each of them independently.
import { useGravityGroup } from "renature"; import "./styles.css"; export default function App() { const [nodes] = useGravityGroup(3, (i) => ({ from: { transform: "translate(0px, 0px) scale(1) skewY(0deg)", opacity: 0 }, to: { transform: "translate(20px, 20px) scale(1.2) skewY(5deg)", opacity: 1 }, config: { moverMass: 10000, attractorMass: 10000000000000, r: 10 }, repeat: Infinity, delay: i * 500 })); return ( <div className="App"> <div className="container"> {nodes.map((props, i) => { return <div className="box" key={i} {...props} />; })} </div> </div> ); }
We used the useGravityGroup
to create the grouped animation above. All grouped animation hooks take a similar form:
const [props] = use<Force>Group(n: number, fn: (index: number) => Config);
You can delay animations in renature
by specifying the delay
property in your animation configuration. delay
expects a number in milliseconds and will start the animation once the specified delay
has elapsed. This is most commonly used for grouped animations where you want to stagger children animating at regular intervals.
Let’s look at how to control animation states in renature
. We can start, stop, pause, delay, and run animations a specific number of times. We’ve already looked at how to repeat and delay animations.
To control animation states, renature
provides a controller
API. The controller
is the second object returned by a renature
hook, and it comes with three methods — start
, pause
, and stop
— for interacting with your animation’s play state.
import { useFriction } from "renature"; import "./styles.css"; import Button from "./Button"; export default function App() { const [props, controller] = useFriction({ from: { transform: "translateY(0px)", opacity: 1, borderRadius: "10%" }, to: { transform: "translateY(50px)", opacity: 0, borderRadius: "50%" }, repeat: Infinity, pause: true //Signal that the animation should not run on mount. }); return ( <div className="App"> <div className="btn-box"> <Button action={controller.start} text="start" /> <Button action={controller.pause} text="pause" /> <Button action={controller.stop} text="stop" /> </div> <div className="box" {...props} /> </div> ); }
We use the controller.start
method available in the controller
API to start animations. To do so, we need to include pause: true
in our animation configuration to prevent the animation from immediately running on mount.
To pause a running animation, we use the controller.pause
method. This method will stop the frame loop but preserve the animation state. This means we can resume an animation at any time using controller.start
.
To stop a running animation, we use the controller.stop
method. Unlike controller.pause
, this will destroy the animation state, so we should only use it when we’re certain we want the animation to end.
renature
demosNow that we know how to create animations with renature
, let’s look at some real-world applications.
Have you ever hovered over a card and seen the image scale up or down along the width and height of its container? Let’s recreate that effect using renature
.
Here’s a sandbox of what we will build:
While CSS is not the focus of this topic, we will need some CSS for the effect to work as it should.
.card-box { width: 200px; border: 1px solid grey; overflow: hidden; margin-bottom: 2rem; }
We have to set an overflow: hidden
to the card-box
div to ensure that when the image scales, it remains restricted in its container. With that explained, let’s get back to renature
.
Using what we learned about controlling animation states, we call the controller.start
and the controller.pause
methods whenever we hover in and out of the img-box
div.
export default function Card({ imgUrl, props, controller }) { return ( <div className="card-box"> <div className="img-box" {...props} onMouseEnter={controller.start} onMouseLeave={controller.pause} > <img src={imgUrl} /> </div> //more stuff below...
Notification toasts are among the common components used in applications. Let’s create one using renature
.
Here’s a sandbox of what we will build:
For the CSS, we had to set the opacity of the toast to 0 so that it only shows when the button is clicked.
return ( <div className="App"> <button onClick={controller.start}>show toast</button> <Toast props={props} /> </div> );
We call the controller.start
method when the button is clicked. This fires the animation, and the toast drops down into view.
Not everyone enjoys decorative animations or transitions, and some users experience outright mental and physical difficulty when faced with parallax scrolling, zooming effects, and so on. As such, we must ensure that our animations don’t present accessibility concerns for application users.
In renature
‘s config object, we can include an optional reducedMotion
property. If this is left unspecified and the end user prefers reduced motion, the animating element will be set immediately to the to
state to avoid potentially harmful animation.
While I’ve made some demos in this article showing how renature
can be used in real-world applications, it would be awesome if the team included more practical demos that might be useful to developers. Regardless, renature
is an awesome physics-based animation library.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.