withastro / starlight

🌟 Build beautiful, accessible, high-performance documentation websites with Astro
https://starlight.astro.build
MIT License
4.79k stars 514 forks source link

Preserve navigation scroll position/toggle state on page change #307

Closed duncanwerner closed 12 months ago

duncanwerner commented 1 year ago

What version of starlight are you using?

0.4.1

What version of astro are you using?

2.7.4

What package manager are you using?

npm

What operating system are you using?

Linux (WSL)

What browser are you using?

Chrome/Edge/Firefox

Describe the Bug

If you have a very long set of navigation links, the scroll position resets on page transitions. The same is true for toggle state, which makes the page look jumpy on transitions even without a lot of pages. It's not the end of the world but it is annoying. It would be preferable to maintain these across page changes.

This can be done using some script and it's not too heavy weight. It would be nice if this were included. Alternatively if there was a way to add custom scripts to the default layout -- something like customCss that would let me insert an Astro component or even just some TS -- I could do it myself without forking starlight.

Link to Minimal Reproducible Example

No response

Participation

delucis commented 1 year ago

Thanks for the issue. Would be nice to improve this indeed.

For a simple solution in your own project, you can add scripts using the head config option: add your JS to the public/ directory, and then add it to your site’s head in astro.config.mjs:

  head: [{ tag: 'script', attrs: { src: '/my-script.js' } }],
duncanwerner commented 1 year ago

Sorry, I missed that. I thought I had scoured for places to insert stuff.

I would still prefer a way to inline it (I know I can inline it in that head object, but I mean as a proper module) but this works great for the time being. If anyone is interested here's my working example (scroll position only):


(() => {

  const key = 'toc-position';

  let timeout = 0;

  let position = { scrollTop: 0 };

  const sidebar = document.querySelector('#starlight__sidebar > .sidebar-content');

  if (sidebar) {

    const data = sessionStorage.getItem(key);
    if (data) {
      try {
        const obj = JSON.parse(data);
        position = {
          ...position, ...obj,
        };
        sidebar.scrollTop = position.scrollTop || 0;
      }
      catch (err) {
        // ...
      }
    }

    sidebar.addEventListener('scroll', () => {
      position.scrollTop = sidebar.scrollTop;
      if (!timeout) {
        timeout = window.setTimeout(() => {
          sessionStorage.setItem(key, JSON.stringify(position));
          timeout = 0;
        }, 100);
      }
    });

  }

})();
astridx commented 1 year ago

I just tried this and it works great. Thank you @duncanwerner for sharing. Now the only issue is that the open-state of the detail-elements is not saved. After clicking on a menu item, all previously closed ones open again.

This means that the scoll position is not correct in this case.

Am I missing something or is it not possible to save this state at the moment?

duncanwerner commented 1 year ago

Here's our current version, which saves the detail state:

https://gist.github.com/duncanwerner/d49fd9574ecd7a26d1febd0077dd012b

Example:

https://docs.treb.app

You are welcome to use it, just remember that it is fragile to future changes in Starlight.

stereobooster commented 1 year ago

I wonder if @swup/astro with morph config would do the trick

Faf4a commented 1 year ago

Wondering, any progress on this?

doroved commented 1 year ago

Yes, I wish scroll state saving was built into starlight

wassfila commented 1 year ago

I had a similar issue, and also worse, with websites having a high pages count, the menu building on each page slowed down my build time considerably. So I designed this client side shamefully injected menu with vanilla js, the advantage is the open closed state storage and the menu data are fetched only once https://github.com/MicroWebStacks/astro-big-doc/blob/main/src/layout/client_menu.js but from Starlight I would expect more, closed state and position indeed and I was wondering if it is not possible to keep the whole menu state with ViewTransition.

pReya commented 1 year ago

Any reasons why the solution of @duncanwerner couldn't be merged into the project? Looks like a solid implementation to me.