I've noticed some odd behaviors with view transitions and scroll restoration in Chrome and I'm trying to understand if these are bugs or it is intended behavior. I can't find any official information on how they are supposed to behave in combination with scrolling.
I've created a fiddle that emulates an SPA with two pages: one with a small blue ball at the end of the page and one with a large blue ball at the top of the page.
If I
- scroll to the end of page 1
- click "next" to go to page 2
- scroll up on page 2
- press back
Chrome will
- instantly scroll down page 2 to restore the scroll position
- start an animation of the ball that starts at its new position (that is higher on the page than it was when i pressed back)
- and then smoothly animate to page 1
That it first restores the scroll position before starting the animation results in a discontinuity in the animation that IMO completely destroys the point of it.
Am I doing something wrong, e.g. is there something wrong with how I do the SPA functionality, or is this a Chrome bug, or is it intended behavior?
--
This is the code:
let page = 0;
function render() {
let body = '';
if (page == 0) {
for (let i = 0; i < 13; i++) {
body += `<p>.</p>`;
}
body += `<div style="height: 30px; width: 30px; border-radius: 100%; background-color: blue; view-transition-name: bar"></div>`
} else {
body += `<div style="height: 300px; width: 300px; border-radius: 100%; background-color: blue; view-transition-name: bar"></div>`
for (let i = 0; i < 13; i++) {
body += `<p>.</p>`;
}
}
document.querySelector('#content').innerHTML = body;
document.querySelector('a').innerHTML = page == 0 ? 'Next' : 'Back';
}
render();
const useViewTransitions = true;
function transition() {
if (useViewTransitions) {
document.startViewTransition(render);
} else {
render();
}
}
document.querySelector('a').addEventListener("click",
async function(event) {
event.preventDefault();
if (page == 0) {
page++;
history.pushState({
page
}, '');
transition();
} else {
history.back();
}
}
);
window.addEventListener("popstate", ({
state
}) => {
page = state ? .page || 0;
transition()
})
with the following HTML
<div style="height: 30px; width: 30px; border-radius: 100%; background-color: red"></div>
<div id="content">
</div>
<a href="#"></a>
--
I hope the above description was comprehensible. Unfortunately I can't post a video, but this is the instant before I press back on page two (scrolled to the top of the page):
And this is an instant later when it scrolls me down before starting the animation
I ran into a similar problem with a view transition that involved a scroll. My solution was to defer slightly at the beginning and end of the transition function, e.g.
I think there is a slight race condition between creating the snapshot for the transition and setting the scroll position.