janosh / svelte-toc

Sticky responsive table of contents component
https://janosh.github.io/svelte-toc
MIT License
113 stars 6 forks source link

Anchor link on page load + Chrome + smooth scroll + plain Svelte does not work #57

Closed tomtseng closed 4 months ago

tomtseng commented 4 months ago

I have a plain Svelte (not SvelteKit) website. If I add <ToC />, give my page the CSS style scroll-behavior: smooth, and load the website directly to anchor link (e.g., localhost:5173/#anchor) in Chrome, it does not scroll to the anchor.

Expected behavior

Scroll to the anchor on page load.

Actual behavior

Remain at the top of the page.

Steps to reproduce

  1. Create a plain Svelte project: run npm create vite@latest and select Svelte as the framework.
  2. Install svelte-toc: cd svelte-project, npm install, npm install -D svelte-toc.
  3. Replace App.svelte with the following:
    
    <script lang="ts">
    import Toc from 'svelte-toc'
    </script>
{#each {length: 2} as _, i}

Heading {i}

{#each {length: 100} as _}

Lorem ipsum

{/each} {/each}

4. Launch the website: `npm run dev`
5. Navigate to `localhost:5173/#1` in Google Chrome.

Instead of scrolling down to "Heading 1" as expected, we remain at the top of the page. (Anchor links work fine after page load — after loading `localhost:5173/#1`, we can go to `localhost:5173/#0` and `localhost:5173/#1` and the anchors work.)

## Versions

* macOS Sonoma 14.5
* Google Chrome: 125.0.6422.142
* Svelte: 4.2.12
* Vite: 5.2.0
* svelte-toc: 0.5.8

## Detailed description

Related to issue #36. 

In Chrome, navigating directly to an anchor link with smooth scrolling causes fires multiple `scrollend` events immediately on page load. svelte-toc's `scroll_to_active_toc_item()` fires on `scrollend` to scroll the ToC, but since [Chrome can't handle simultaneous scroll events](https://issues.chromium.org/issues/40572042), the scroll to the anchor link is canceled.

In SvelteKit the bug doesn't occur because the ToC only gets mounted after those initial `scrollend` have already fired.
janosh commented 4 months ago

thanks for the detailed analysis! are those initial page-load scrollend events distinguishable in any way so we can ignore them? otherwise, seems like the only hacky workaround would be to only start listening for scrollend after a given cooldown period, i.e. similar to what you're doing in https://github.com/AlignmentResearch/KataGoVisualizer/pull/123 (except the component would still mount immediately)

tomtseng commented 4 months ago

Hmm the spurious initial scrollend events seem to happen before any scroll events happen, so maybe a fix is to have <Toc /> disable its scrollend handler until the first scroll event occurs

janosh commented 4 months ago

maybe worth a try to add the passive modifier to the scrollend event?

- on:scrollend={() => {
+ on:scrollend|passive={() => {

if you can modify your Toc.svelte in node_modules, i'd be curious if anything changes. might be good to add for performance reasons anyway (unless svelte already inserts it automatically).

tomtseng commented 4 months ago

I added passive and re-ran npm fun dev, but it didn't fix — issue is still present

janosh commented 4 months ago

thanks for trying! i'll attempt a fix tomorrow