4

I have a redux app which uses requestAnimationFrame to render a new state on every TICK, however Google Chrome seems to be calling requestAnimationFrame up to 3 times(!!) for some ticks, and not at all for others, leading to janky animations.

In React development mode: Chrome performance timeline screenshot 1 In React NODE_ENV=production mode: Chrome performance timeline screenshot 2

It's rendering at 60fps already, so multiple unnecessary requestAnimationFrame callbacks per 16ms frame are highly wasteful of CPU resources. Frames that don't have a tick also cause laggy-ness in the animations. My tick function only takes 4ms to compute (well under the 16ms budget), and I only have one callback running on the page.

Here is the animation code that handles dispatching my redux call on every tick.

class AnimationHandler {
    constructor(store) {
        this.store = store
        this.animating = false
        store.subscribe(::this.handleStateChange)
    }
    handleStateChange() {
        if (!this.animating) {
            this.tick()
        }
    }
    tick(timestamp) {
        this.animating = true
        this.store.dispatch({
            type: 'TICK',
            current_timestamp: (new Date).getTime(),
            // reducer handles updating rendered state to this point in time
        })
        window.requestAnimationFrame(::this.tick)
    }
}

window.animations = new AnimationHandler(window.store)  // redux store

What could be causing Chrome to do this, and how can I make it so that there's one consistent tick() call per frame rendered, no more no less?

Nick Sweeting
  • 5,364
  • 5
  • 27
  • 37
  • 1
    having the same problem! did you happen to find an answer? – treeseal7 Nov 01 '17 at 01:01
  • 1
    It got better when I started using the `NODE_ENV=production` environment variable to compile React in production-mode. I never found out the root cause unfortunately. – Nick Sweeting Nov 01 '17 at 03:25

1 Answers1

0

I had the same problem. It was a bug on my side and I suspect it always is.

In your code , calling tick() schedules an infinite loop of requestAnimationFrame since requestAnimationFrame() is always called as part of tick(). This is normal.

It's not a problem if tick() is called only once outside of the loop. It's not happening in your example, but it's likely that you initiated several loops in the real application.

Just in case, I would change your code to:

handleStateChange() {
        if (!this.animating) {
            this.animating = true // immediate
            this.tick()
        }
    }

In my case, I was reusing the tick() method, adding layers of requestAnimationFrame().

Example: (WRONG!)

// init
window.requestAnimationFrame(tick)

// on some other events
tick() // WRONG! will create a new layer of requestAnimationFrame loop!
Offirmo
  • 18,962
  • 12
  • 76
  • 97