eduter
eduter

Reputation: 71

How to execute two animations sequentially, using react-spring?

I tried chaining two springs (using useChain), so that one only starts after the other finishes, but they are being animated at the same time. What am I doing wrong?

import React, { useRef, useState } from 'react'
import { render } from 'react-dom'
import { useSpring, animated, useChain } from 'react-spring'

function App() {
  const [counter, setCounter] = useState(0)
  const topRef = useRef()
  const leftRef = useRef()
  const { top } = useSpring({ top: (window.innerHeight * counter) / 10, ref: topRef })
  const { left } = useSpring({ left: (window.innerWidth * counter) / 10, ref: leftRef })

  useChain([topRef, leftRef])

  return (
    <div id="main" onClick={() => setCounter((counter + 1) % 10)}>
      Click me!
      <animated.div id="movingDiv" style={{ top, left }} />
    </div>
  )
}

render(<App />, document.getElementById('root'))

Here's a codesandbox demonstrating the problem: https://codesandbox.io/s/react-spring-usespring-hook-m4w4t

Upvotes: 3

Views: 4427

Answers (2)

lawrence-witt
lawrence-witt

Reputation: 9354

I did some digging as this was puzzling me as well and came across this spectrum chat.

I'm not sure I totally understand what is going on but it seems the current value of the refs in your code is only read once, and so when the component mounts, the chain is completed instantly and never reset. Your code does work if you put in hardcoded values for the two springs and then control them with turnaries but obviously you are looking for a dynamic solution.

I've tested this and it seems to do the job:

const topCurrent = !topRef.current ? topRef : {current: topRef.current};
const leftCurrent = !leftRef.current ? leftRef : {current: leftRef.current};

useChain([topCurrent, leftCurrent]);

It forces the chain to reference the current value of the ref each time. The turnary is in there because the value of the ref on mount is undefined - there may be a more elegant way to account for this.

Upvotes: 1

eduter
eduter

Reputation: 71

I just found out that there's a much simpler solution, using only useSpring:

function App() {
  const [counter, setCounter] = useState(0)

  const style = useSpring({
    to: [
      { top: (window.innerHeight * counter) / 5 },
      { left: (window.innerWidth * counter) / 5 }
    ]
  })

  return (
    <div id="main" onClick={() => setCounter((counter + 1) % 5)}>
      Click me!
      <animated.div id="movingDiv" style={style} />
    </div>
  )
}

Example: https://codesandbox.io/s/react-spring-chained-animations-8ibpi

Upvotes: 2

Related Questions