React Rive Animations getting stuck
Hi there. I'm building a React app with various Rive animations, and loving it. Sporadically the animations get stuck. It's like they are pausing for some undetermined reason. The onPause callback is not being fired. I have no way to know when it happens programmatically, and thus no way to make them play again.
It's difficult to reliably reproduce.
In my scoreboard component, player cards leap frog to the top. The Rive Avatars that are rendered underneath the leap frogging player cards pretty consistently freeze, and sometimes their animation begin again, and sometimes not. I'm not sure why they freeze in the first place. You can see in this video, Shiloh, Addison, and Rowan are all frozen. Each avatar is its own Rive component using useRive, and the player card scoreboard animations are handled with Framer Motion.
But it happens for various unknown reasons, like going to another tab and then coming back.
Has anyone had this issue?
We've determined that this is a bug in the React Rive runtime. Our current solution is to call rive.startRendering every 100ms to "unstick" our animations. An intersectionObserver is causing rive.stopRendering to be called, but due to a race condition, startRendering is sometimes not called.
Hi! thanks for reporting the issue. Is it possible that in the uploaded video the issue is not happening? I'm not seeing frozen avatars and I can't find the names "Shiloh, Addison, and Rowan".
In any case, it might be helpful to know how these components are getting reordered.
Are they a list of elements each with a key, where the keys could be pointing to a different content after the reorder for example?
Thanks!
Oops, I uploaded the wrong video. Here is the correct one.
got it, yes the ones that move down seem to be the ones that freeze. How are they being moved? are they absolute positioned?
Thanks so much for replying so quickly!
The elements are indeed in a list that is being reordered and the animation is handled by framer motion. The keys for each item in the list are the players' ids, and the content does not change.
Here's an example with a much slower animation.
Notice how all the avatars in the background stop animating. I believe that rive.stopRendering() is being called on them in the runtime, but the problem is that only some of them receive rive.startRendering() and we end up in a bad state.
Nowhere in the code do we call pause or stop, nor do the onPause or onStop callbacks get fired.
We added a click event to each avatar that calls rive.startRendering, it unfreezes them and returns them to normal. So we know that rive.startRendering solves our problem.
This happens all the time in our app. Not just during this scoreboard animation. It happens when we resize the window, go to another tab and come back, when other players join. Sometimes it just seems to happen sporadically.
The code we think is causing the problem: https://github.com/rive-app/rive-react/blob/8bb5652df020f20bf641a2fc480a59c08cdd093b/src/hooks/useRive.tsx#L150-L164
I've been trying to reproduce the issue but haven't succeeded yet. Would you be able to create a small sample code where this issue is reproduced?
Looking at the code for our intersection observer, I can't see anything that jumps out immediately.
I'm wondering if framer motion does any type of offscreen rendering, or whether it rewrites elements outside the react lifecycle causing this.
I've been able to reproduce it with a Reorder component from framer-motion. It looks like framer-motion either changes the size or moves the component off screen somehow, but when it comes back, the IntersectionObserver is not triggered.
I can also confirm that in Safari and Firefox it works correctly. So this looks like a Chrome bug
Good catch! I'm also unable to reproduce outside Chrome. That's a bummer since our app will predominantly run in a Chromium browser.
Our fix for now is to wrap useRive
in our own hook that sets an interval to call startRendering()
every 100ms. Obviously not ideal, but better than having our animations freeze.
We're looking for a temporary solution on our side. In the meantime, have you tried listening to framer-motion events? I'm not very familiar with the library, but if there's some "onDrop" event that you can listen to and resume the rive animation immediately, it should work.
I've seen in github that you mention it can trigger for other reasons like resizing the window, or leaving the tab. I haven't been able to reproduce those, but could all those be related to framer-motion components?
I don't think so. Our other examples are of components that are not wrapped in Framer Motion components. It's difficult to reproduce consistently. I'll send a code snippet if I can find a reliable way to repro. Thanks
Hi! We've published a new react version 4.10.0.
It fixes the issue that we've been able to reproduce.
It also adds a "shouldUseIntersectionObserver" parameter to the hook that you can pass as "false" to fully opt-out of the functionality in case it's still happening for you.
Amazing! Thank you! I'll update our package and check it out.
Awesome! let us know how it goes.
And thanks for the help tracking it!
Hey Hernan, I updated to the latest Rive packages, and unfortunately we're still experiencing the issue. I believe it's solved for the case we were able to reproduce. However, we've got a bit of a unique scenario. We're building a Discord Activity, so our app runs in Chromium on Discord. We can fairly consistently reproduce the bug by running the activity in Discord and while it's running if you switch to a text channel and then return to the activity, the Rive animations will be frozen. The problem continues to be resolved by calling startRendering in an interval on each of our animations.
I tried removing the interval and adding shouldUseIntersectionObserver: false,
to each of our useRive hooks, and the animations freeze when I go to a text channel and come back.
I don't know how to reproduce this in Chrome directly. Maybe it's a bug in Discord. I just don't understand how stopRendering would be called without the intersectionObserver. Maybe it's not being called at all... All I know is that startRendering solves the issue.
In this video, I have the latest versions of @rive-app/canvas
and @rive-app/react-canvas
and I've passed shouldUseIntersectionObserver: false,
to all useRive calls. When I first start the activity, the characters in the bottom right animate when hovered, and the characters in the top left and top right animate continuously. I check out a few text channels and when I come back all the characters are frozen. The hover animations don't work and the characters at the top have stopped.
Hey, there is one other scenario where we don't draw anymore and it also uses an observer.
https://github.com/rive-app/rive-wasm/blob/master/js/src/rive.ts#L2039
Could you try adding a breakpoint there to see if at some point _hasZeroSize is true?
By the way, is there a link that you can share for us to reproduce the problem?
Oh interesting. That could be it. I'm not sure how to add a breakpoint there. Can't find that js code in the devtools, just the wasm. Tried symlinking to a local version of the package, but changes to the js files don't seem to have an effect.
As for a link to the Discord Activity, I'll see if I can send you something tomorrow.
Thanks for all your help.
I'm reviewing https://github.com/rive-app/rive-wasm/blob/master/CONTRIBUTING.md
Seems like a path to adding a breakpoint, albeit a bit of a long one... :)
lol I'd try to set the breakpoint in Chrome's source files. Perhaps searching for "_hasZeroSize" works?