choojs / choo

:steam_locomotive::train: - sturdy 4kb frontend framework
https://choo.io/
MIT License
6.78k stars 595 forks source link

Flicker on route change + window scroll in Safari #683

Closed istvanvasil closed 6 years ago

istvanvasil commented 6 years ago

I am facing a very irritating behaviour when using window.scrollTo(0, 0) with Choo in Safari.

I am sure others have encountered this problem as it seems to be present on other sites built with Choo. Including choo.io

I have a scroll function that scrolls the window to the top when navigating to other pages on a website

module.exports = scroll

function scroll (state, emitter) {
  emitter.on('pushState', function () { 
     window.scrollTo(0, 0)
  })
}

app.use(require('./plugins/scroll'))

This works smooth as butter in Chrome. User navigates to another route, route changes and window is scrolled to position.

In Safari when you scroll down on a page and decide to navigate somewhere else the window seems to scroll to top a fraction of a second before the route has changed. This creates a flash of the content from the top part of the pages, overlapping with the new route's content.

Only workaround I've found is to call window.scrollTo(0, 0) locally inside each view. No flicker this way but removes the possibility to navigate back to the previous page and land at the same scrolling position as before, which also gets annoying when you are browsing a page.

Tried to use setTimeout() to trick the browser but this creates some weird freeze loop effect when navigating back to the previous page using the trackpad on my computer (haven't tested but this probably same on mobile which is even a bigger problem).

Any ideas?

Thank you :)

tornqvist commented 6 years ago

I believe this is due to a bug in Safari which schedules animation frame callbacks after the next repaint, which goes against the spec which dictates that calls to requestAnimationFrame should be resolved before repaint. More on this here: https://www.youtube.com/watch?v=cCOL7MC4Pl0

Edit: the part about Safari inconsistency is at the 23 minute mark in linked video

What I usually do to work around this particular situation is to schedule the scrollTo an extra animation frame:

module.exports = scroll

function scroll (state, emitter) {
  emitter.on('pushState', function () { 
    window.requestAnimationFrame(function () 
      window.scrollTo(0, 0)
    })
  })
}
istvanvasil commented 6 years ago

Thank you! @tornqvist