React-Spring-1 useSpring(), config, To() interpolate render, useTransition(), React-use-measure.
React-spring is a library to create animated UI components.
npm i react-spring
App.js
import { animated, useSpring } from '@react-spring/web'
We import the animated higher-order component, a function that takes a component and returns a new one.
We useSpring({}) hook to set up the animation object properties (from, to), then we deconstruct it on the style property to trigger it on mount.
//we append the component on the animated high-order tag
function MyComponent() {
const springs = useSpring({
from: { x: 0 },
to: { x: 100 },
})
return (
<animated.div
style={{
width: 80, height: 80,
background: '#ff6d6d',
borderRadius: 8,
...springs
}}
/>
)
}
<div> <MyComponent /> </div>

The <animated> component can be used directly on the JSX with a useString() as a style prop.
We can use the useSpringRef() hook to reference the imperative API on event handlers.
We need to declare the api in the useSpring() and the function syntax, if we want the useState() dependency to work.
Any difference between the useSpring() and the api.method from:{} will be skipped.
//You can also use a reverse+dependency usaState() to trigger the useSpring()
import { animated, useSpring, useSpringRef } from '@react-spring/web'
let refe = useSpringRef()
let [init, initApi] = useSpring(()=>({
from: {x: 0, transform: "rotate(0deg)"},
ref: refe,
//reverse: true/false
}), [dependency])
//from() isn't needed and to can be an array of [{props}]
function mosso(){
refe.start({
from: {x: 50, transform: "rotate(30deg)"},
to: {x: 150, transform: "rotate(330deg)"},
config: {duration: 5500},
loop: {reverse: true}
})
}
<animated.div
onClick={mosso} className="boxo"
style={{ ...init }}
>
</animated.div>

We can useSpringRef() to pause/resume and stop animations.
//after stop() it won't resume()
init.pause()
init.resume()
init.stop()
//We can do the same on a useSpring() api.
The useSpring() config property contains mass, tension, friction, easing, and duration (used for its "timing function")
//duration stops working when using any timing function prop
const basic = useSpring({
from: {background: 'brown'},
config:{
tension: 280, friction: 120,
easing: "easeOutQuart", duration: 3000,
},
}
//We can edit the config on the api event handler function.
const handleClick = () => {
basic.start({
y: 20,
config: {
friction: 10,
},
})
}
We adapt the CSS @keyframe{} animation with useSpring(), using the {easings.steps()} hook for the animation-timing-function.

To() interpolate style and text render
Using the to method, we interpolate the springValue data on a different style property (we can animate both), and access its pure value (without the spring Object).
// 0/360 used for rotate and translateX if the useSpring is included
import { animated, useSpring , config, to } from '@react-spring/web'
const props = useSpring({
from: { x: 0 },
to: { x: 360 },
config: {duration: 2500}
})
//we use x springValue both as rotate/x transform
<animated.h2 style={{
transform: props.x.to(value => `rotateX(${value}deg)`),
...props
}}>
{ props.x.to(value => value) } //0-360
</animated.h2>
We use the springValue as a breakpoint to animate a style property.
const auto = useSpring({
from: { x: 0 },
to: { x: 1 },
config: {duration: 3000}
})
style= {{
transform: auto.x
.to([0, 1], [0, 100])
.to((x) => `translate(${x}px)`),
}}
We useState() a conditional springValue and the to() method to animate style props.
Range are the animated springValues breakpoints, output are the style property values on each breakpoint, and both only accept integers in their array.
//We need {} on the springValue to access the to() method
//We don't need the from{} property
const [vedo, setVedo] = useState(false)
const {dice} = useSpring({
dice: vedo ? 1 : 0,
config: {duration: 5000},
})
//only color strings will work, and they will be only rendered as rgb()
<animated.div
onClick={ ()=> setVedo(!vedo) }
className="boxo"
style={{
scale: dice.to({ range: [0, 1], output: [0.5, 1.5] }),
background: dice.to({ range: [0, 0.5, 1], output: ["red", "orange", "pink"] }),
y: dice.to({ range: [0, 0.8, 1], output: [0, 50, 50] }),
x: dice.to({ range: [0, 1], output: [0, 200] }),
}}>
</animated.div>

We can trigger a CSS animation with useSpring() by interpolating a class string. The delay is equal to the current CSS animation duration.
//We need a [dependency] and the reverse prop to trigger it onClick()
let [preso, setPreso] = useState(false)
let [giro, apigir] = useSpring(()=> ({
from: { text: "outside" },
to: [
{ text: "outside animate", delay: 1000},
{ text: "outside", delay: 2500 }
],
reverse: preso
}), [preso])
//For some reason it doesn't work with className
<animated.div class={giro.text.to(value => value)}>
<div className="inside"></div>
</animated.div>
A useState() conditional useSpering() prop.
We use an array of to style objects for multiple animations, the array has to include the from object (any object before it will be ignored).
//A loop: true would skip to the starter point, we need to add reverse.
//This will trigger the animation on mount
let auto = useSpring({
from: { x: 0, y: 0, scale: 0.5, background: "red"},
to: [
{ x: 0, y: 0, scale: 0.5, background: "red" },
{ x: 100, y: -50, scale: 1, background: "orange" },
{ x: 200, y: 50, scale: 1.5, background: "pink" },
],
config:{ duration: 1000 },
loop:{ reverse: true }
})
<animated.div className="boxo" style={auto}>
</animated.div>
We animate a useSpring() background image using only output (will work on its first 2 values).
//It can't even be set/triggered onClick()
const {value} = useSpring({
from: {value: 0}, to: {value: 1},
loop: {reverse: true},
config: {duration: 6000}
})
let x2= value.to({ output: ['0%', '20%'] })
<div className="conta">
<animated.div className="suono" style= {{ x: x2 }} >
</animated.div>
</div>
When animating a CSS background image, we use to cover the X/Y empty margins.
//A bigger background avoids empty border spaces during the animation
.conta{
position: relative;
width: 85vw;
height: 30vh;
overflow: hidden;
background-color: burlywood;
}
/*use inset: -200%; width: 400%; height: 400%; for bigger animations*/
.treno{
inset: 0% -25%;
width: 125%; height: 100%;
position: absolute;
opacity: 0.3;
background: url('https://media.timeout.com/images/105782103/image.jpg') center;
background-size: cover;
}

We can directly destruct a useSpring() object and re-assign property names.
//The destructured property then the assigned name
let {opacity, transform: tran} = useSpring({
opacity: 0.5,
transform: rotateX(180deg)
})
<animated.div style={{ opacity }}></animated.div>
<animated.div style={{ transform: tran }} > </animated.div>

The useTransition() animation styles and methods
The useTransition(array, config) hook sequentially animates datasets of elements on the DOM.
//enter{} is the current style, from/leave are before/after the transition
//exitbefore will repeat the transition to remove the current element before the next
const transitions1 = useTransition([content], {
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
exitbefore: true,
config: { duration: 5000 },
})
//useTransition() can start() with useSpringRef().
useTransition() triggers automatically, we useEffect() to bind it to events.
We create a useState() index and an array of components to render with the useTransition()
import { useTransition, animated, useSpringRef } from '@react-spring/web'
const [indice, setIndice] = useState(0)
//Each component will have the useTransition style prop as an argument.
//useful for when cycling different types of components
const pages = [
({ style }) => (
<animated.div style={{ ...style, background: "pink"}}>A</animated.div>
),
...
]
The useTransition() onRest method triggers each time an animated transition is completed.
The useTransition() renders one element at a time and the onRest() method updates its index to re-trigger the transition.
//useTransition() triggers each time its array changes.
//onRest(animationResult-object, spring controller,current index)
const transitions1 = useTransition(indice, {
from: { opacity: 0, y: "50%"},
enter: { opacity: 1, y: "0%"},
leave: { opacity: 0, y: "0%"},
config: { duration: 2000 },
onRest: (string, crtl, item)=> {
if (indice === item) {
setIndice(x => (x+ 1) % 3 )
}
}
})
The useTransition() hook returns the style object and its array content (an useState() index)
<div>
{transitions1((style, content) => {
const Page = pages[content]
return <Page style={style} />
})}
</div>

To render multiple <animated> elements we put an array as a useTransition() argument.
//The elements will overlay if position: absolute
let lista = [{ numba: 1,chiave: "unn"}, ... ]
//The map() is not needed, it is to contrast with the next example
let multipli = useTransition(
lista.map((x)=> ({...x})),
{
key: (item)=> (item.chiave),
from: {opacity: 0},
enter: { opacity: 1, background: "red", x: 20, margin: 10 },
leave: {opacity: 0}
}
)
//we can use the array properties with item.chiave
<div>
{multipli( (style, item, t, index)=>(
<animated.div className="boxo text-center position-absolute" style={{...style}}>
<p className='text-white'>Index {index}</p>
</animated.div>
))
}
</div>

Changing the order of its useState() array doesn't trigger the useTransition() animation by itself.
We need to dynamically create style properties, from external variables, during the array methods, which we extract in the enter object.
The update() property animates its extracted style properties each time the array argument changes, it also requires the property value to be set using an external counter.
//Update reacts on property changes, contrary to add/remove, shuffle doesnt trigger
//Each update will translate3D() the previous x properties values.
let y= 0;
let colore = useTransition(
lista1.map((x)=> ({ ...x, x: y+= x.numba*25}) ),
{
key: (item)=> (item.chiave ),
from: { opacity: 0 },
update: ({x}) => ({x}),
enter: ({x}) => ({ opacity: 1, background: "red", x}),
leave: { opacity: 0 },
}
)
const rein = () => setLista1(shuffle)

React-use-measure on events and react-spring animations
The React-use-measure tool references DOM elements to measure their boundaries.
//The bounds objects contain { width, height, top, right, bottom, left, x, y}
npm i react-use-measure
import useMeasure from 'react-use-measure'
const [ref, bounds] = useMeasure()
function sopra({ clientX, clientY }){
setXY([clientX, clientY])
}
<div className="inside" ref={ref} onMouseMove={sopra}>
<div className="box"></div>
</div>
<div>
<p> Mouse x {Math.round(xy[0] - bounds.left)}px </p>
<p> Mouse y {Math.round(xy[1] - bounds.top)}px </p>
</div>

We access the from/to useSpring() properties with:
props.width.animation.to //the to:{} width value
props.width.to(x => x.toFixed(0)) //Dom rendering inside an <animated />

The matchMedia() method returns true if the document matches (or is higher) than its media-query string.
//The matching media query index on the value array returns the column's integer
//If width > 1000 then matchMedia("(min-width:1000px)").matches == true
//We add/remove an eventListener ("resize") to update the useState() match value
function useMedia(queries, values, defaultValue) {
function match(){
return values[queries.findIndex(q => matchMedia(q).matches)] || defaultValue
}
const [value, set] = useState(match)
useEffect(() => {
const handler = () => set(match)
window.addEventListener("resize", handler)
return () => window.removeEventListener("resize", handler)
}, [])
return value
}
const colonne = useMedia(
['(min-width: 1100px)', '(min-width: 900px)', '(min-width: 600px)'], [5, 4, 3], 2)
console.log(colone) //3
We useMeasure() to dynamically set the column's width on media queries.
//On resize we can get the current width and useMedia() columns
const [ref, { width }] = useMeasure()
let wide = width / colonne
<div ref={ref} className="text-center">
<p> Each of the {colonne} columns will be {wide} px</p>
</div>

We import an array of image objects as a useState(), to use in the useTransition().
export default [
{ css: 'https://images.pexels.com/.../pexels-photo-416430.jpeg', height: 150 },
...
]
import Data from './Data';
const [items, set] = useState(Data)
We import the lodash.shuffle method to useEffect() shuffle the useState(Data).
//npm install lodash is a javascript utility library
//or npm install lodash.shuffle if it doesn't work
import shuffle from 'lodash.shuffle'
console.log( shuffle([1,2,3,4] ) //[3,2,4,1]
useEffect(() => {
const t = setInterval(() => set(shuffle), 5000)
return () => clearInterval(t)
}, [])
We set the (container) height and the useTransition() images objects array on a useMemo().
//its useMedia() dependencies change on resize
//The heights array length and values get updated on items.map()
const [heights, gridItems] = useMemo(() => {
let heights = new Array(columns).fill(0)
let gridItems = items.map((child, i) => {
const column = heights.indexOf(Math.min(...heights))
const x = (width / columns) * column
const y = (heights[column] += child.height / 2) - child.height / 2
return { ...child, x, y, width: width/ columns, height: child.height / 2 }
})
return [heights, gridItems]
}, [columns, items, width])
On shuffle the images translate-X/Y, change column array position, and the (max) height container is updated.
//trail is the delay of images overlapping during the transition
//we deconstruct the useTransition() animated argument properties
const transitions = useTransition(gridItems, {
key: item => item.css,
from: ({ x, y, width, height }) => ({ x, y, width, height, opacity: 0 }),
enter: ({ x, y, width, height }) => ({ x, y, width, height, opacity: 1 }),
update: ({ x, y, width, height }) => ({ x, y, width, height }),
leave: { height: 0, opacity: 0 },
config: { mass: 5, tension: 500, friction: 100 },
trail: 5,
})
<div ref={ref} className="list" style={{ height: Math.max(...heights) }}>
{transitions((style, item) => (
<animated.div style={style}>
<div style={
{ backgroundImage: `url(${item.css}?auto=compress&dpr=2&h=500&w=500)`}
} />
</animated.div>
))}
</div>

Last updated
Was this helpful?