Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is requestAnimationFrame called at the right point? #2569

Closed
jakearchibald opened this issue Apr 21, 2017 · 29 comments
Closed

Is requestAnimationFrame called at the right point? #2569

jakearchibald opened this issue Apr 21, 2017 · 29 comments
Labels
addition/proposal New features or enhancements

Comments

@jakearchibald
Copy link
Collaborator

jakearchibald commented Apr 21, 2017

I'm repurposing this slightly.

Currently the spec says that requestAnimationFrame callbacks should come before the very next render, unless requestAnimationFrame is called after the browser has started processing the raf callback queue, in which case the callbacks happen before the following render.

Chrome & Firefox do this, but Edge and Safari don't. They run requestAnimationFrame callbacks before the following render.

test.style.transform = 'translate(0, 0)';

document.querySelector('button').addEventListener('click', () => {
  const test = document.querySelector('.test');
  test.style.transform = 'translate(400px, 0)';
  
  requestAnimationFrame(() => {
    test.style.transition = 'transform 3s linear';
    test.style.transform = 'translate(200px, 0)';
  });
});

In Edge and Safari, the box will move to the left. In Chrome and Firefox (spec compliant) it'll move to the right. Demo.

Developers are pretty confused about this, with currently 60% expecting the Edge/Safari behaviour.

Should we do something about this?

Here's the original issue text:


It's currently impossible to request a callback for the next animation frame.

requestAnimationFrame(cb) will call cb before the next visual update in most cases, unless it's called during "run the animation frame callbacks", in which case it'll be called after the next visual update.

Sometimes you want to wait until after a natural layout/render to perform particular steps, to avoid a synchronous layout.

function requestNextAnimationFrame(cb) {
  requestAnimationFrame(() => requestAnimationFrame(cb));
}

The above guarantees cb will be called after a natural layout/render, but if called during "run the animation frame callbacks" you end up waiting an additional frame.

Another way to solve this problem would be to provide insight into the current position within the event loop, so a user-land implementation of requestNextAnimationFrame could avoid calling raf twice if it's already in that phase of the loop.

@zcorpan zcorpan added the addition/proposal New features or enhancements label Apr 21, 2017
@domenic
Copy link
Member

domenic commented Apr 21, 2017

This seems vaguely related to the discussion in #512 (which got off topic pretty quickly)

@jakearchibald
Copy link
Collaborator Author

It seems like both Edge & Safari treat requestAnimationFrame as "next frame" already (box moves to the left). https://safari-raf-bug.glitch.me/

Replies to https://twitter.com/jaffathecake/status/912601447442927617 suggest developers are very confused about this.

Feels like we should do something to make this kind of scheduling more explicit, but I'm not sure how, yet.

@jakearchibald jakearchibald changed the title requestNextAnimationFrame Is requestAnimationFrame called at the right point? Sep 26, 2017
@jakearchibald
Copy link
Collaborator Author

If the use cases of requestAnimationFrame are:

  • Let me debounce things that happen multiple times a frame.
  • Sync me to the refresh rate of the screen.

…then the spec makes the most sense, and Safari and Edge are needlessly delaying the callbacks. I guess I'll file bugs with them and see if they disagree.

@wanderview
Copy link
Member

Is this covered by any existing WPT cases?

@jakearchibald
Copy link
Collaborator Author

This takes me back to thinking we need something like requestAfterAnimationFrame, which lands after paint. This is the point you'd do all your style/layout-dependent reading, and you'd do all your style/layout writing in requestAnimationFrame.

@jakearchibald
Copy link
Collaborator Author

@wanderview I can't find one at a glance

@wanderview
Copy link
Member

This takes me back to thinking we need something like requestAfterAnimationFrame, which lands after paint.

Or maybe, requestAnimationFrame(cb, { mode: 'after-frame' }). We could have before-frame, after-frame, and the current default of surprise-me.

@annevk
Copy link
Member

annevk commented Sep 27, 2017

@smaug---- also occasionally asks for an "after animation frame callback".

@rocallahan and @heycam might recall some of the original motivation for the timing used by the specification.

@jakearchibald
Copy link
Collaborator Author

@wanderview that works, although it might encourage GC? I think that's why raf uses a callback rather than a promise.

@rocallahan
Copy link

requestAnimationFrame was created before promises existed.

@rocallahan
Copy link

The current spec and Firefox/Chrome behavior let you use requestAnimationFrame to batch work to happen before the next render. That seems useful. I guess I'm agreeing with #2569 (comment).

@rocallahan
Copy link

I'm pretty sure that was the original motivation, though I can't be sure.

@jakearchibald
Copy link
Collaborator Author

jakearchibald commented Sep 27, 2017

@smaug----
Copy link
Collaborator

Yeah, the current behavior feels reasonable. I'm very surprised people think that is confusing.

https://www.w3.org/Bugs/Public/show_bug.cgi?id=28644 is separate thing. Quite often one wants to do something right after rendering. Something which isn't possibly about rendering but something else.

@annevk
Copy link
Member

annevk commented Mar 13, 2018

@rniwa could you maybe add Apple's rationale for doing this after the frame rather than before? Or include the relevant folks who can.

cc @mstange

@rniwa
Copy link
Collaborator

rniwa commented Mar 14, 2018

cc @smfr @hober

@smfr
Copy link

smfr commented Mar 14, 2018

I think WebKit's behavior falls out of our "it's like a timer" thinking. In fact, we fall back to a timer in the implementation in some cases. I agree that the Safari behavior seems wrong in the case where you really want to have changes show up before the up-coming paint.

@jakearchibald
Copy link
Collaborator Author

requestAnimationFrame's timing as specced is really useful for debouncing things like mousemove, where you only need to update once per frame, but waiting an additional frame introduces visual lag.

@jakearchibald
Copy link
Collaborator Author

I believe WebKit is switching to match other browsers here.

@nullhook
Copy link

This issue still persists on Safari.

@smfr
Copy link

smfr commented Dec 12, 2020

This issue still persists on Safari.

Please clarify, with Safari version, more details and a testcase filed at bugs.webkit.org

@07akioni
Copy link

07akioni commented May 17, 2021

@smfr

Safari Version 14.0.3 (16610.4.3.1.7)
Mac OS Big Sur11.2.3 (20D91)

https://safari-raf-bug.glitch.me/

Chrome: left to right
Safari: right to left

@smfr
Copy link

smfr commented May 17, 2021

Thanks for the testcase; I filed https://bugs.webkit.org/show_bug.cgi?id=225871. It's not clear to me that this is about rAF timing; it may be a style resolve or animation bug.

@jakearchibald
Copy link
Collaborator Author

@smfr I wondered if this was related to "click" timing. It isn't, but I noticed another bug at the same time https://bugs.webkit.org/show_bug.cgi?id=225866

@LiChangyi
Copy link

Mac OS Big Sur 11.4
chrome 92.0.4515.131(x86_64) right to left.

@moriwang
Copy link

Hi there, I made a test but I can't explain it.

  • macOS 11.4
  • Chrome 94.0.4606.61
  • Safari 14.1.1

Right to left on both chrome and safari.

document.querySelector('button').addEventListener('click', () => {
  const test = document.querySelector('.test')
  test.style.transform = 'translate(400px, 0)'

  requestAnimationFrame(() => {
    test.style.transition = 'transform 3s linear'
    test.style.transform = 'translate(200px, 0)'
  })
})

Left to right on both chrome and safari.

document.querySelector('button').addEventListener('click', () => {
  const test = document.querySelector('.test')
  test.style.transform = 'translate(400px, 0)'
  test.style.transition = 'transform 3s linear'

  requestAnimationFrame(() => {
    test.style.transform = 'translate(200px, 0)'
  })
})

Why does the position of test.style.transition matter?

@smfr
Copy link

smfr commented Sep 25, 2021

Live test: https://codepen.io/smfr/pen/jOwedOW

document.querySelector('button').addEventListener('click', () => {
  const test = document.querySelector('.test')
  test.style.transform = 'translate(400px, 0)'

  requestAnimationFrame(() => {
    test.style.transition = 'transform 3s linear'
    test.style.transform = 'translate(200px, 0)'
  })
}

On click, the translate is set to 400px, instantaneously. On the next rAF, the 200px translate and transition make it animate from 400px to 200px.

document.querySelector('button').addEventListener('click', () => {
  const test = document.querySelector('.test')
  test.style.transform = 'translate(400px, 0)'
  test.style.transition = 'transform 3s linear'

  requestAnimationFrame(() => {
    test.style.transform = 'translate(200px, 0)'
  })
})

On click, the 400px translate and transition make it animate from 0 to 400px. In the rAF callback the target is now set to 200px, so the transition retargets to 200px.

@aquaductape
Copy link

aquaductape commented Mar 19, 2023

Goes left to right on Firefox 111.0 (64-bit), Mac 13.2.1 https://safari-raf-bug.glitch.me/. It's odd, cuz sometimes it goes right to left on first time page load then is consistently is left to right even after refreshing page/clearing cache, I can't reproduce it.

@aquaductape
Copy link

Still goes left to right on Firefox 124.0 (64-bit), Mac 14.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements
Development

No branches or pull requests