Hey there! So I'm trying to figure out how to use canvas
element in React.
Here is how my playground looks like. Canvas element, a dot travelling around the board and a button to start / stop the animation
The Issue
The button is giving me hard time pausing and resuming the animation. When the dot stops programmatically, it takes a couple of extra clicks on the Start button to keep it moving.
I suspect it has to do with useEffect
and its dependencies.
Do you think you could take a look and give me some advice?
The Code
I use requestAnimationFrame()
method to update the animation.
const reqRef = useRef()
const previousTimeRef = useRef()
const animate = time => {
// some animation
if (previousTimeRef.current !== undefined) {
const deltaTime = time - previousTimeRef.current
}
previousTimeRef.current = time
reqRef.current = requestAnimationFrame(animate)
// stop
if (shouldStop) cancelAnimationFrame(reqRef.current)
}
useEffect(() => {
// start the loop
reqRef.current = requestAnimationFrame(animate)
// clean up
return () => cancelAnimationFrame(reqRef.current)
}, [shouldStop, previousTimeRef.current])
-
animate()
function loops itself -
useEffect()
starts the animation -
requestAnimationFrame()
method generates newreqRef
value with each run - in order to stop the animation you have to use
cancelAnimationFrame(reqRef.current)
with the current reqRef
Approach
I use shouldStop
as a key to pause the animation.
const [shouldStop, setShouldStop] = useState(true)
<button onClick={() => setShouldStop(!shouldStop)}>
At the start it works as expected
- The button flips the key
- useEffect fires, as
shouldStop
is set as its dependency, and sets the loop
if (positionX < 0) {
setPositionX(290)
setPositionY(165)
setShouldStop(!shouldStop)
}
When the dot bounces at the edge, the app resets its position and flips the key back to true
. The dot rests in the middle of the screen.
And now when I press the button, the key switches to false
yet nothing happens. After the second click key switches to true
again. And only on the third time the key switches to false
and the dot starts moving.
So
I guess I have three questions 😼
- Is it a proper approach overall?
- What am I missing about the useEffect()?
- How do you trace / investigate those issues?
Top comments (3)
Unfortunately I found it a bit hard to tell what's wrong because I can't see the whole thing to run it and debug it. It seems like it should be working.
Nevertheless, consider trying something like this to keep the code more clear and maybe fix the issues (depending on what the rest of the code is dong):
Changes made:
cancelAnimationFrame
call inanimate
ifanimate
doesn't decide when the animation will finish. It looks like only the value ofshouldStop
decides whether the animation should run or not, which is not changed insideanimate
.useEffect
, don't start a new animation if it shouldn't be running.Another suggestion, consider changing
shouldStop
(a negative condition) toisAnimating
(a positive condition) and invert the conditionals. Working with double negative conditions likeif (!shouldStop)
is more difficult than working with positive conditionsif (isRunning)
.Good luck :)
Hey Spyros, thanks for suggestions!
I guess I should clarify that in fact I took the requestAnimationFrame() part from someone's example and didn't really change it as long as it's working. Just provided this condition to link the animation to the Stop button.
The way I understand it,
requestAnimationFrame()
does not work as a one line function, it has this intricate way of operating. It works only withing this looping function, generates the reference id with each run. And — I discovered it just now after more research — generates the timestamp each time and passes it back as an argument (time
in my example).You didn't ask 😜, but if you're interested, here's a great reference I found css-tricks.com/using-requestanimat...
Hi @ptifur ,
Do you still need help with this? If so, would you mind creating a minimal reproducible example on something like CodeSandbox? That would make it easier for us to see what's going on :)
What I can say is that whenever you need to set state based on a previous value, like when you're flipping
shouldStop
, instead of doingsetShouldStop(!shouldStop)
, trysetShouldStop(prevShouldStop => !prevShouldStop)
. This ensures that you're always using the latestshouldStop
to update it instead of accidentally using a previous value: reactjs.org/docs/hooks-reference.h...