inertiajs / inertia

Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
https://inertiajs.com
MIT License
6.36k stars 428 forks source link

Does not reliably restore scroll position using browser/mouse nav buttons in Firefox. #1698

Closed benjivm closed 1 day ago

benjivm commented 1 year ago

Version:

Describe the problem:

Browser/mouse button page navigation (forward/back) does not restore scroll position. Oddly, it does appear to work fine if the content is static (not served from a controller, e.g.)

Steps to reproduce:

Reproduction repo: https://github.com/benjivm/inertiajs-firefox-scroll-position

Open the page in Firefox (this issue does not affect Chrome).

Scroll down to about the middle of the page where the link and explanation are then navigate with your browser's nav buttons or mouse button.[

This polyfill makes it more reliable but is jarring to use.

This relates to this issue #1459

Hasan-Mir commented 1 year ago

I have the same issue (mostly in Firefox but I think I saw it a few times in Chrome as well).

ivalkenburg commented 11 months ago

Having the same issue. It does not always scroll to top when navigating. Specially when further down the page. Very odd. Maybe its more likely firefox bug rather then inertia?

fritz-c commented 7 months ago

Inertia's popstate listener (popstate being the event called when the back button is used) will asynchronously load the previous component, rendering it when it has loaded. https://github.com/inertiajs/inertia/blob/f34f373692a964933e4272fc49fa48552301e568/packages/core/src/router.ts#L480-L492

The problem arises in that the browser wants to restore the previous scroll position as soon as the popstate listener exits, when the url changes, and at that point in time, the previous component has not loaded yet. So what you might be seeing is your pre-back page getting scrolled down (by browser native restoration) but hitting the bottom of the scroll bar, after which point the previous component gets rendered, just at the scroll position you managed to get to from the previous page.

I looked for an elegant solution around this issue, but failing to find one, made one that at least looks good from an end-user's perspective. It's for Vue+Typescript, but could be adapted for other environments. You would want to run this only once, probably in a global layout or something.

import { onMounted, onUnmounted } from 'vue';
import { debounce } from 'lodash-es';
import { router } from '@inertiajs/vue3';

/**
 * Restores the window scroll position when navigating back to a page.
 *
 * The 'popstate' event handler in inertia loads the component from the previous
 * page asynchronously, so browser-native scroll restoration is triggered before
 * the new component is mounted, resulting in the scrollbar banging against the
 * bottom of the window in the case that the page before navigating back is
 * smaller in height than the scroll position to be restored. This composable
 * separately tracks the window scroll position, saves it in the history state
 * and restores it immediately after navigation, avoiding a flash of the
 * incompletely restored scroll position.
 */
export function useWindowScrollRestoration() {
    const saveWindowScroll = debounce((event: Event) => {
        if (event.target === document)
            router.remember(window.scrollY, 'windowScrollY');
    }, 100);
    function restoreWindowScroll() {
        const windowScrollY = router.restore('windowScrollY');
        if (typeof windowScrollY === 'number') {
            window.scrollTo(0, windowScrollY);
        }
    }

    let removeListener = () => {};
    onMounted(() => {
        // Disable browser-native scroll restoration
        window.history.scrollRestoration = 'manual';

        window.addEventListener('scroll', saveWindowScroll, true);
        removeListener = router.on('navigate', restoreWindowScroll);
    });
    onUnmounted(() => {
        window.history.scrollRestoration = 'auto';
        window.removeEventListener('scroll', saveWindowScroll, true);
        removeListener();
    });
}
pedroborges commented 3 weeks ago

@benjivm @Hasan-Mir @ivalkenburg @fritz-c please test if #1980 fixes this issue for you.

benjivm commented 3 weeks ago

@pedroborges I installed using npm install inertiajs/inertia#master, will this get me the correct changes for testing?

I still see the issue in the repro repo: https://github.com/benjivm/inertiajs-firefox-scroll-position

pedroborges commented 3 weeks ago

@benjivm it's not trivial because Inertia.js uses a monorepo, here's how I did it on one of my projects:

npm i -D https://gitpkg.now.sh/inertiajs/inertia/packages/core?master
cd node_modules/@inertiajs/core
npm i
npm run build
rm -rf node_modules

npm i -D https://gitpkg.now.sh/inertiajs/inertia/packages/svelte?master
cd node_modules/@inertiajs/svelte
npm i
npm run build
rm -rf node_modules

v1.3 will be tagged next week 🤞

benjivm commented 3 weeks ago

I'm using Vue and so changed the 2nd group of commands to the Vue3 package, and unfortunately still see the issue.

Rubrasum commented 3 weeks ago

@benjivm I am also using vue3. After trying for a while I found I probably needed to go the long way around, through the contribution guide and clone it, then setup a playground for my app. I took a noob-shot at setting up through a basic package.json change could not get it.

I also have this problem and will check on the release that is fixed. Here is summary of my problem:

I could not get anchor links to work AND consistent scroll position on back/forward navigation. The anchor link problem was fixed by removing smooth scrolling.

EXAMPLE: If I click an anchor link to another page on my basic lead generation site:

<Link :href="file.href" class="w-full relative"> ... Link to other page and section ... </Link>

I am automatically scrolled to the element (so long as I do not use smooth scrolling). If I use smooth scrolling it scrolls to the wrong location (ok, fine, not a big deal) . But if I press back in either case, the correct page loads at the incorrect scroll position. The position is usually lower than expected.

Here's an example of what I am talking about: https://youtu.be/rfQxhHv5Szs

I will check back at the new release to see if this fixes it. I'm nearing the end of a project so I'd rather not share the repo right now, hope that's ok.

Here's my current versions though:

npm (relevant dependencies and devdependencies together)

        "@inertiajs/inertia": "^0.11.1",
        "@inertiajs/progress": "^0.2.7",
        "@inertiajs/server": "^0.1.0",
        "@inertiajs/vue3": "^1.2.0",
        "laravel-vite-plugin": "^1.0",
        "vite": "^5.0",
        "ziggy-js": "^2.3.0",
        "@vitejs/plugin-vue": "^5.0.0",

composer versions

        "laravel/sail": "^1.29",
        "php": "^8.2",
        "inertiajs/inertia-laravel": "^1.0",
        "laravel/framework": "^11.0",
        "laravel/jetstream": "^5.1",
        "laravel/sanctum": "^4.0",
        "laravel/tinker": "^2.9",
        "tightenco/ziggy": "^2.3"
rodrigopedra commented 2 weeks ago

~@Rubrasum how did you remove smooth scrolling?~

Never mind. I got it, you disable on Firefox's preferences, right?

I can confirm that disabling smooth scrolling seems to fix the scroll restore.

Although this is not a fix per se, as this options is toggled by default on new Firefox installs, maybe it can guide to better understand how to work around it, or to report a bug to Firefox.

Rubrasum commented 2 weeks ago

~@Rubrasum how did you remove smooth scrolling?~

Although this is not a fix per se, as this options is toggled by default on new Firefox installs, maybe it can guide to better understand how to work around it, or to report a bug to Firefox.

It was part of my own css. I'm using Laravel, Tailwind, and Inertia Vue3. So I have an app.css file here resources/monumental_theme/css/app.css with these line at the top:

@tailwind base;
@tailwind components;
@tailwind utilities;

html {
    scroll-behavior: smooth;
}

So do a search for "scroll-behavior: smooth" Or Tailwind's "scroll-smooth" class.

rodrigopedra commented 2 weeks ago

In my case, I didn't have any string "smooth" on my generated CSS. Then I remembered this Firefox's setting:

Screenshot_20240924_190700

Toggling it off did the trick for me.

Rubrasum commented 2 weeks ago

Lol, great another bug you found for me too then.

Instead of removing the smooth line, I should've added auto. If you specifically add auto it will override the default firefox setting. I just tested it very haphazardly and seemed to work.

rodrigopedra commented 2 weeks ago

I'm out of office right now, tomorrow I'll give it a try and let you know.

thanks!

rodrigopedra commented 2 weeks ago

Hey @Rubrasum, that seems to fix it! Thanks a lot!

html {
    scroll-behavior: auto;
}
benjivm commented 1 day ago

Inertia v2.0.0-beta.1 appears to resolve this issue for me without resorting to the scroll-behavior CSS mentioned above.