View transitions and scroll restoration

448 views Asked by At

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):

enter image description here

And this is an instant later when it scrolls me down before starting the animation

enter image description here

1

There are 1 answers

0
Alexander Early On

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.

document.startViewTransition(async () => {
  await 1;

  updateDOM();
  window.scrollTo({top: scrollY});
  
  await 1;
})

I think there is a slight race condition between creating the snapshot for the transition and setting the scroll position.