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>
Spring animation object + onClick api

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>
useSpring(9 state and event handler function with useSringRef()

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,
    },
  })
}
List of useString() presets configs

We can use both a preset and a custom config property.

const basic = useSpring({
  from: {background: 'brown'},
  config: config.wobbly,
  config:{
    duration: 3000,
  },
}

There are the presets configs.

default – { tension: 170, friction: 26 }
gentle – { tension: 120, friction: 14 }
wobbly – { tension: 180, friction: 12 }
stiff – { tension: 210, friction: 20 }
slow – { tension: 280, friction: 60 }
molasses – { tension: 280, friction: 120 }

We adapt the CSS @keyframe{} animation with useSpring(), using the {easings.steps()} hook for the animation-timing-function.

Absolute window and single sprite animation.

On a container we animate a position-absolute window, for the actual movement, and the sprite image, for the steps() animation.

We can modify the background-image sprite, but we must keep its proportions in the container (48/26 = 96/52).

//The sprite needs to have a transparent background.
//The steps() is the number of frames in the image
//The first step is the width while the last is the steps(total)- 1 

<div className='position-relative' style={{ width: 48, height: 26 }}>
  <animated.div className="position-absolute" 
    style={{ height: "100%", width: "100%", left: lefto }}>

    <animated.div className="image1" style={{ 
        height: "100%", width: "100%", backgroundSize: "cover",
        backgroundImage: `url(${transCat})`,
        backgroundPosition
      }}>
    </animated.div>

  </animated.div>
</div>

//The useSpring({}) is the @keyframe{from{} to{}} body
//easings.steps() is the animation-timing-function
let [{lefto}, leftoApi] = useSpring(()=>({
  from: { lefto: "0px"}, 
  to:{ lefto: "200px" },
  config: {
    duration: 5000,
    easing: easings.steps(20)
  }
}) )

//This is the actual sprite movement 
//The backgroundPosition moves the background in the absolute-window.
let [{backgroundPosition}, posiApi] = useSpring(()=>({
  from: {
    backgroundPosition: "50px"
  },
  to:{
    backgroundPosition: "-574px"
  },
  config: {
    duration: 5000, 
    easing: easings.steps(13),
  },
  loop: true
}) )
easing.steps() sprite animations

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>
Animated style properties onClick()

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>
CSS animation on an useSpring() interpolated string

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;
}
Animated useSpring() background image

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>
Destruct useSpring() rotateX property on 2 display absolute images

The 2 absolute images are overlayed in the same container.

<div className="carta" onMouseEnter={ronda} onMouseLeave={ronda}>
  <animated.div className="carta davanti">
  </animated.div>

  <animated.div className="carta dietro">
  </animated.div>
</div>

//The event handlers are on the container coz the images will be rotated
.carta{
  position: absolute;
  width: 200px;
  height: 120px;
}

.davanti,
.dietro{
  background-size: cover;
}

.davanti{
  background-image: url(https://images.neon.com);
}

.dietro{
  background-image: url(https://images.sea.com);
}

We useState() the useSpring() destructured style properties, and use them on the animated components.

//We use perspective() for a better rotateX effect, or add rotateY()
const [conta, setConta] = useState(false)

let {opacity: opa, transform: tran } = useSpring({
  opacity: conta ? 1 : 0,
  transform: `perspective(600px) rotateX(${conta ? 180 : 0}deg) `,
  config: {duration: 1000}
})

const ronda = () => { setConta(!conta) }

Both images share the useSpring() style properties, the front image opacity needs to be opposite from the back one, we edit it using the to method.

//we add an opposite rotateX to the back image to avoid it being upside down.
<div className="carta" onMouseEnter={ronda} onMouseLeave={ronda}>

  <animated.div 
    className="carta davanti"
    style={{ 
      opacity: opa.to(x=> 1- x),
      transform: tran
    }}
  >
  </animated.div>

  <animated.div 
    className="carta dietro"
    style={{ 
      opacity: opa, transform: tran,
      rotateX: "-180deg"
    }}
  >
  </animated.div>
</div>
rotateX and opacity useString() on 2 absolute images

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>
automatic useTransition() updated onRest()
useTransition() index useState() to render different components

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>
useTransition() multiple elements

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)
useTransition() to set and update style properties with imported objects.

We npm install lodash (javascript library) and import the shuffle method.

import shuffle from 'lodash.shuffle'
shuffle([1,2,3,4,5])	//[3, 5, 4, 2, 1]

We useState() an array of style objects to render and animate with useTransition(). We useEffect() setInterval() to trigger the shuffle method.

//we need to cleanInterval() it before re-starting

export default [
  {
    name: 'Rare Wind',
    description: '#a8edea → #fed6e3',
    css: 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)',
    width: 120,
  },
  ...
]

import data1 from './Data1'
const [lista, setLista] = useState(data1)

useEffect(() => {
  const t = setInterval(() => setLista(shuffle), 2000)
  return () => clearInterval(t)
}, [])

We update the counter on the array.method argument and to set the style property (x).

//We update the counter and set spring objects with the imported object
let spazio = 0

const transitions = useTransition(
  lista.map(data => ({ ...data, x: (spazio += data.width) - data.width })),
  {
    key: (item) => item.name,
    from: { width: 0, opacity: 0 },
    update: ({ x }) => ({ x }),
    enter: ({ x, width }) => ({ x, width, opacity: 1, height: 80 }),
    leave: { width: 0, opacity: 0 },
  }
)

We use the counter to style the container, the spring object to style the <animated>, and the imported object properties to style the single-child components.

<div className="list1" style={{ width: spazio }} onClick={mischia} >

  {transitions( (style, item, t, index) => (
    <animated.div className="card1" style={{ ...style }}>

      <div className="details1" style={{ backgroundImage: item.css }} />

    </animated.div>
  ))}
</div>

The components share the same space (width) with position: absolute.

.card1 {
  position: absolute;
  height: inherit;
  padding: 15px;
}

.details1 {
  height: 100%;
}
useTransition() on {x} updated style property

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>
Relative Cursor position on measured DOM element

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 />
Animating measures with useSpring() conditional values.

We animate a react-use-measure width with a useState() trigger, using a ternary operator to change the useSpring() target value (to).

The relative>absolute child component is animated using its parent measured width.

//we need the animated component to access the useSpring() prop

const [open, toggle] = useState(false)
const [ref, { width }] = useMeasure()
const props = useSpring({ width: open ? width : 0})

<div ref={ref} className="main" onClick={()=> toggle(!open) } >
  <animated.div className="fill" style={props} />

  <animated.div className="content">
    {props.width.to(x => x.toFixed(0))}
  </animated.div>
</div>

The children CSS properties set its starter animation condition (from).

//the .fill element starts from the left and its height covers the parent
.main {
  position: relative;
  width: 200px;
  height: 50px;
  cursor: pointer;
  border-radius: 5px;
  border: 2px solid #272727;
  overflow: hidden;
}

.fill {
  position: absolute;
  left: 0;
  height: 100%;
  background: hotpink;
}

.content {
  position: absolute;
  display: flex;
  color: #272727;
  width: 100%; height: 100%;
  align-items: center; justify-content: center;
}

The useString() api for the event works too, but we can't reset the (from) property yet.

const [propa, api] = useSpring(()=>({
  from: {width: 0 }
}))

function checkin(){
  api.start({
    from: {
      width: 0,
    },
    to: {
      width: width,
    },
  })
}

<div ref={ref} className="main" onClick={checkin} >
  <animated.div className="fill" style={propa} />

  <animated.div className="content">
    {propa.width.to(x => x.toFixed(0))}
  </animated.div>
</div>
Animated width property on useState()

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>
columns and with on resize

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>
useTransition() image grid
codeSandbox of the github example

Last updated

Was this helpful?