Releases: FormidableLabs/renature
🔚 Fix Animation Completion
This release fixes a small regression in the way animations complete. When we introduced the repeat API in v0.9.0, we also introduced more complexity around when animations are considered "done". This resulted in occasional instances where an animation wouldn't reach its exact to state; for example, an opacity animation from 0 to 1 might complete at opacity: 0.99927361. This release fixes that behavior to ensure we reach the exact final to state in both repeated and non-repeated animations.
Fixed
⚠️ All animations reach their exacttostate on completion, orfromstate for odd-numberedrepeatcounts. PR by @parkerziegler here.
🔁 The repeatType API
This release adds support for a new repeatType API. repeatType describes how repeated animations should run. Valid values are 'loop' and 'mirror'; 'mirror' is the default.
If 'loop' is specified, the animation will run from its from state to its to state, and then "jump" back to the from state to start the animation over. This is akin to animation-direction: normal in CSS animations.
If 'mirror' is specified, the animation will alternate between its from and to values. This is the behavior renature has always had and is the default if repeatType is not specified. It is akin to animation-direction: alternate in CSS animations.
Added
- The
repeatTypeparameter can now be passed to configure how repeated animations should be run. PR by @parkerziegler here.
🐛 Fix onAnimationComplete
This release fixes a small bug in the onAnimationComplete implementation. In #122, we accidentally introduced a regression by not passing the onAnimationComplete callback to the internal onComplete function. This release addresses that regression.
Fixed
- The
onAnimationCompletecallback is called when an animation completes if supplied by the end user. PR by @parkerziegler here.
Access to Motion Vectors in onFrame
This release adds in a slight modification to the onFrame API. Previously, onFrame only gave you access to a single value, progress. progress is a decimal number between 0 and 1 representing the progress of an animation, with 0 being the beginning and 1 being the end. useGravity2D was an exception; since this hook animates infinitely by default, progress is not a defined value and was never supplied.
In v0.10.1, you can now access the physics motion vectors (position, velocity, and acceleration) of your animating element inside of the onFrame callback, like so:
const [props] = useFriction<HTMLDivElement>({
from: {
transform: 'scale(1) rotate(0deg)',
},
to: {
transform: 'scale(1.5) rotate(720deg)',
},
onFrame: (progress, { position, velocity, acceleration }) => {
// Execute some code on each call to requestAnimationFrame using any of the four values above.
// For example, we could modify the document body's opacity to match the x component of the velocity vector.
document.body.style.opacity = velocity[0];
},
});For useGravity2D, the API is just slightly different, with no progress argument supplied:
useGravity2D({
config: {
attractorMass: 1000000000000,
moverMass: 10000,
attractorPosition: [center.left, center.top],
initialMoverPosition: [center.left, center.top - 100],
initialMoverVelocity: [1, 0],
threshold: {
min: 20,
max: 100,
},
timeScale: 100,
},
onFrame: ({ position, velocity, acceleration }) => {
// Execute some code on each call to requestAnimationFrame!
},
});Changed
- ✨ The
onFramecallback now receives the motion vectors of the animating element as arguments. PR by @parkerziegler here.
Accessible Animations API
This release adds a new API to support accessible animations in renature. Consumers now have a first-class API for defining the from / to configuration to use if a user has the prefers-reduced-motion: reduce media feature enabled.
To enable accessible animations, add a reducedMotion configuration object with from / to keys in your hook's configuration, like so:
const [props] = useFriction<HTMLDivElement>({
from: {
transform: 'scale(1) rotate(0deg)',
},
to: {
transform: 'scale(1.5) rotate(720deg)',
},
reducedMotion: {
from: {
opacity: 0,
},
to: {
opacity: 1,
},
},
});With this configuration, a user who specifies prefers-reduced-motion: reduce will see opacity animated from 0 to 1. Likewise, a user whose device or operating system does not implement the prefers-reduced-motion media feature will see opacity animated from 0 to 1. In this way, we enable the accessible animation by default. Finally, if a user has prefers-reduced-motion: reduce and no reducedMotion object is specified, the user will see no animation and the animated object will be immediately transitioned to the to state in a single frame update.
Added
- ✨ The
reducedMotionconfiguration object was added to support accessible animations onuseFriction,useFrictionGroup,useGravity,useGravityGroup, anduseFluidResistanceanduseFluidResistanceGrouphooks. PR by @parkerziegler here.
Fixed
🐛 Fixes to the Repeat API
This release fixes bugs uncovered in the new repeat API outlined in #124.
Fixed
- Specifying an even number for
repeatno longer leads to an incorrect "jump" to thetovalue specified in the hook config. PR by @parkerziegler here. - Erroneously specifying a
stringvalue forrepeatno longer results in an animation extending beyond its bounds. While this will be caught at compile-time by TypeScript users, it's an easy mistake for JavaScript users to make, so we guard against it at runtime. PR by @parkerziegler here.
🔁 The Repeat API
This release introduces a new API to renature hooks — the repeat API.
Previously, the only way to run an animation more than one iteration was to use the infinite parameter, set to true. However, this doesn't work for cases where you want your animation to run a set number of iterations before coming to a smooth stop.
To support this, the repeat parameter replaces the infinite parameter in renature hooks. You can still create an infinite animation by specifying repeat: Infinity, but you can also animate between from and to a set number of times. For example, the following configuration:
const [props] = useFluidResistance<HTMLDivElement>({
from: {
boxShadow: '20px 20px 0px teal, -20px -20px 0px orange',
},
to: {
boxShadow: '-20px -20px 0px orange, 20px 20px 0px teal',
},
config: {
mass: 20,
rho: 20,
area: 20,
cDrag: 0.1,
settle: false,
},
repeat: 2,
});indicates that, after the animating element has finished its first animation cycle (e.g. has animated from the from state to the to state), it should repeat the animation two additional times.
Changed
⚠️ Theinfiniteparameter forrenaturehooks has been deprecated in favor ofrepeat. You can now repeat arenatureanimation declaratively a set number of iterations before stopping. To run an animation infinitely, specifyrepeat: Infinity. PR by @parkerziegler here.
📦 Reduce Bundle Size
This release is a small, internals-only, performance-related release. It consolidates the core logic of the hooks renature exposes into an internal useForceGroup hook that all hooks compose.
Changed
All hooks now compose the internal-only useForceGroup hook to centralize animation logic. PR by @parkerziegler here.
1️⃣ Fix controller.set with a supplied index parameter
This release fixes a small bug related to the use of the optional index parameter in the controller.set API. controller.set with the index parameter specified now works as documented.
Fixed
- The
controller.setAPI correctly targets an animating element when called with the index parameter specified. PR by @parkerziegler here.
➡️ controller.set API
This release adds support for a set method on the controller returned by a renature hook. You can use controller.set to animate an element to any CSS state at any time.
To use the controller.set API, just call controller.set with an object containing the CSS properties you want to animate to. For example, to animate an element to a random scale and opacity value every 2 seconds, you can do the following:
import React, { useEffect, FC } from 'react';
import { useFriction } from 'renature';
export const FrictionSet: FC = () => {
const [props, controller] = useFriction<HTMLDivElement>({
from: {
opacity: 0,
},
to: {
opacity: 1,
},
config: {
mu: 0.5,
mass: 300,
initialVelocity: 10,
},
});
useEffect(() => {
const intervalId = setInterval(() => {
// Call controller.set with a random scale transform and opacity!
controller.set({
transform: `scale(${Math.random()})`,
opacity: Math.random(),
});
}, 2000);
return () => {
clearInterval(intervalId);
};
}, [controller]);
return <div className="mover mover--magenta" {...props} />;
};The controller.set API is an imperative escape hatch to allow for orchestrating more complex animations after the initial declarative animation has run. You can also animate to new states simply by updating the to property of the hook definition.
Added
- A
setmethod is now available oncontrollerto imperatively animate to a new CSS state. PRs by @parkerziegler here and here.
Fixed
eslint-plugin-importwas added to standardizeimportstatements across the codebase. PR by @jzandag here.