flackr / scroll-timeline

A polyfill of ScrollTimeline.
Apache License 2.0
939 stars 91 forks source link

Scroll-Driven Animations in Safari hang after traversing the browser’s history … unless you have Web Inspector open #146

Closed bramus closed 1 year ago

bramus commented 1 year ago

Now here’s a weird situation … in Safari, when traversing the history, animations are not restored correctly. They just hang. What’s even weirder is that from the moment you try to debug this – by opening Web Inspector - the issue is gone.

https://github.com/flackr/scroll-timeline/assets/213073/ec0adb0c-bda1-48a6-842b-41f639efbdbe

By the looks of it the animations get paused, but never tick forwards. I’m guessing there’s some caching issue going on, which needs investigation.

bramus commented 1 year ago

I’ve narrowed this down to a Page Cache (BFCache) problem.

On first page load, when animationstart events get triggered, the original animation gets paused, and the ProxyAnimation gets played instead. This ProxyAnimation instance is stored in a cache (a WeakMap called proxyAnimations) so that it doesn’t get rebuilt whenever an animation restarts as you scroll up/down on the page.

https://github.com/flackr/scroll-timeline/blob/5fab1816ad4f13e2cef3cd27ce6b243027a4970f/src/scroll-timeline-css.js#L168-L171

Internally, upon calling proxyAnimation.play(); a chain of calls happens, eventually leading to addAnimation being called to internally store the animation.

https://github.com/flackr/scroll-timeline/blob/5fab1816ad4f13e2cef3cd27ce6b243027a4970f/src/scroll-timeline-base.js#L197-L209

It’s in this addAnimation where things go wrong. In that function, there’s a bailout mechanism: if the animation is already there, just return. It’s that part that doesn’t play nice with BFCache …


So now, that we know what’s going on, how can we fix this?

I’ve tried to manually tick the animation forwards just before the early return in addAnimation, but this has some other side effects such as it constantly being re-triggered when the animation restarts as the element re-enters the scrollport. Instead of happening all the time, this manual ticking forwards should only happen in case the ProxyAnimation was loaded from the proxyAnimations cache. That would require passing a flag all the way down to addAnimations, so that it would know.

To not fiddle with the polyfill’s internals too much, I came up with another workaround: simply clear the proxyAnimations cache when the page gets unloaded using the unload event. That way, when going back, new ProxyAnimation instances get created and all works fine :)

I’m about to file a PR with this change.