Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation to the same route fails (anchor, hash) #1668

Closed
ParachuteCat opened this issue Aug 4, 2017 · 18 comments · Fixed by #3592
Closed

Navigation to the same route fails (anchor, hash) #1668

ParachuteCat opened this issue Aug 4, 2017 · 18 comments · Fixed by #3592
Labels
fixed on 4.x This issue has been already fixed on the v4 but exists in v3 has workaround improvement

Comments

@ParachuteCat
Copy link

Version

2.4.2

Reproduction link

https://jsfiddle.net/ParachuteKadse/pL0sehq5/show/

Steps to reproduce

  1. Click on Foo
  2. Click Foo-3
  3. Scroll up
  4. Repeat step 2

What is expected?

Scroll down to Foo-3, like the first time.

What is actually happening?

Screen stays at the same position and doesn't scroll to Foo-3.

Special notes

You can see the correct URL in the status-bar of your browser
(Tested on Chrome)

@svewag
Copy link

svewag commented Oct 6, 2017

I made a workaround by using the method $router.push instead of the router-link component.

This method takes a success callback (in this case irrelevant) as 2nd and a abort callback as 3rd parameter.
$router.push(route, success, abort)

These callbacks will be called when the navigation either successfully completed (after all async hooks are resolved), or aborted (navigated to the same route, or to a different route before current navigation has finished), respectively.

see https://router.vuejs.org/en/essentials/navigation.html

@speir-wang
Copy link

@ParachuteKadse Hey mate, I came across the same problem, have you solved this one? If so, would you mind sharing your solution here?

Cheers

@CharlesKumar
Copy link

savedPosition, is only available if this is a popstate navigation (triggered by the browser's back/forward buttons).
The object could be in the form of:

  • { x: number, y: number }
  • { selector: string, offset? : { x: number, y: number }} (offset only supported in 2.6.0+)

If a falsy value or an empty object is returned, no scrolling will happen. read more

he popstate event is only triggered by performing a browser action, such as clicking on the back button (or calling history.back() in JavaScript)... read more

savedPosition is meant for save the scroll position object when using back in browser. (AFAIK)

official example also suggests use hash for anchor behavior

https://github.com/vuejs/vue-router/blob/dev/examples/scroll-behavior/app.js

Your expected behavior can be achieved by vue-scroll plugins

or use standalone solution using #ids with libraries like jump, zenscroll or other similar libraries

@sem4phor
Copy link

sem4phor commented Mar 8, 2018

Possible workaround:

this.$router.push({ name: 'home' }, undefined, () => { location.href = this.$route.hash })
As the 3rd argument is the abort() function, it may have unwanted side effects though..

If you want to use it globally, add a function to your Router:

pushWithAnchor: function (routeName, toHash) {
    const fromHash = Router.history.current.hash
    fromHash !== toHash || !fromHash
    ? Router.push({ name: routeName, hash: toHash })
    : Router.push({ name: routeName, hash: fromHash }, undefined, () => { window.location.href = toHash })
  }

And use it in components with:
this.$router.options.pushWithAnchor('home', '#fee-calculator-section')

@aldencolerain
Copy link

aldencolerain commented Mar 8, 2018

I think it would be fantastic to improve the anchor support. I ran into quite a few questions and issues around this. After reading what everyone has written above I thought I'd paste my solution. I created an anchor-link component composed of a router-link that is used in conjunction with the scrollBehavior function. I haven't thoroughly tested it but here is the basic idea (I think this is the same route check could be improved):

anchor-link.vue

<template>
  <span @click="navigate"><router-link ref="link" :to="to"><slot></slot></router-link></span>
</template>

<script>
  export default {
    name: 'anchor-link',
    props: ['to'],
    methods: {
      navigate() {
        const current = this.$router.currentRoute.fullPath;
        const href = this.$refs.link.$el.getAttribute('href');
        if (current === href) {
          location.href = this.to.hash;
        }
      }
    }
  };
</script>

router.js excerpt

function scrollBehavior(to, from, savedPosition) {
  if (to.hash && document.querySelector(to.hash)) {
    return { selector: to.hash };
  }
  if (savedPosition) {
    return savedPosition;
  }
  return false;
}

const router = new Router({ mode: 'history', routes, scrollBehavior });

None of these solutions really work on reloading the page I tried stuff like below but there obviously a race condition, it doesn't really work. Not sure how to come up with a good solution for anchor pages on reload. I feel like we might be competing with the browser's default behaviour. Maybe someone with more experience could lend some insight?

    mounted() {
      const anchor = this.$router.currentRoute.hash;
      this.$nextTick(() => {
        if (anchor && document.querySelector(anchor)) {
          location.href = anchor;
        }
      });
    }

@gspain
Copy link

gspain commented Apr 13, 2018

I gave up on hacks and workarounds. I've tracked this issue down to lines 1916-1923 in vue-router.js. Commenting the if statement out seems to work, though I'm sure it would be better if it were updated to support same hash navigation.

if (
  isSameRoute(route, current) &&
  // in the case the route map has been dynamically appended to
  route.matched.length === current.matched.length
  ) {
  this.ensureURL();
  return abort()
}

@ranaclyde
Copy link

The @aldencolerain 's implementation worked perfectly for me

    mounted() {
      const anchor = this.$router.currentRoute.hash;
      this.$nextTick(() => {
        if (anchor && document.querySelector(anchor)) {
          location.href = anchor;
        }
      });
    }

@SilverDragon135
Copy link

I went with @gspain solution. I didn't noticed any implications yet (few weeks).

@ajb413
Copy link

ajb413 commented Nov 12, 2018

This worked for me: My Vue Router uses the default mode (hash). The way I see it, because my URLs have 2 hash symbols, the browser behavior of scrolling to a #some-id at the end of the URL breaks. I used code from earlier in this thread to make my own fix that only restores the scrolling feature, instead of changing the history stack. Also, my default scroll behavior is to always begin at the very top.

This works whether you inter-site link, navigate directly using the address bar, or refresh the page.

router/index.js

const router = new Router({
    routes: [
        // route objects here
        // ...
    ],
    scrollBehavior(to, from, savedPosition) {
        return {
            x: 0,
            y: 0,
        };
    },
});

main.js

import Vue from 'vue';
import App from './App';
import router from './router';

Vue.config.productionTip = false;

const fixIdScrolling = {
    watch: {
        $route(to, from) {
            const currentRoute = this.$router.currentRoute;
            const idToScrollTo = currentRoute.hash;
            this.$nextTick(() => {
                if (idToScrollTo && document.querySelector(idToScrollTo)) {
                    document.querySelector(idToScrollTo).scrollIntoView();
                }
            });
        },
    },
};

/* eslint-disable no-new */
new Vue({
    mixins: [fixIdScrolling],
    el: '#app',
    router,
    components: {App},
    template: '<App/>',
});

@vinstah
Copy link

vinstah commented Nov 30, 2018

This worked for me: My Vue Router uses the default mode (hash). The way I see it, because my URLs have 2 hash symbols, the browser behavior of scrolling to a #some-id at the end of the URL breaks. I used code from earlier in this thread to make my own fix that only restores the scrolling feature, instead of changing the history stack. Also, my default scroll behavior is to always begin at the very top.

This works whether you inter-site link, navigate directly using the address bar, or refresh the page.

router/index.js

const router = new Router({
    routes: [
        // route objects here
        // ...
    ],
    scrollBehavior(to, from, savedPosition) {
        return {
            x: 0,
            y: 0,
        };
    },
});

main.js

import Vue from 'vue';
import App from './App';
import router from './router';

Vue.config.productionTip = false;

const fixIdScrolling = {
    watch: {
        $route(to, from) {
            const currentRoute = this.$router.currentRoute;
            const idToScrollTo = currentRoute.hash;
            this.$nextTick(() => {
                if (idToScrollTo && document.querySelector(idToScrollTo)) {
                    document.querySelector(idToScrollTo).scrollIntoView();
                }
            });
        },
    },
};

/* eslint-disable no-new */
new Vue({
    mixins: [fixIdScrolling],
    el: '#app',
    router,
    components: {App},
    template: '<App/>',
});

Thanks heaps this should be implemented

@pkkid
Copy link

pkkid commented Jul 23, 2019

The changes from @ajb413 mostly work, except when reloading the page or navigating from an external site. Anyone else having those issues?

@RobertDiebels
Copy link

RobertDiebels commented Oct 25, 2019

@pkkid Having the same issue. Both scrollBehavior and fixIdScrolling only trigger when the app is changing a route not when the Vue application is first entered nor when the route remains the same.

Personally I'd love to see some functionality that allows me to override that behavior.

@moh1434
Copy link

moh1434 commented Mar 10, 2020

The @aldencolerain 's implementation worked perfectly for me

    mounted() {
      const anchor = this.$router.currentRoute.hash;
      this.$nextTick(() => {
        if (anchor && document.querySelector(anchor)) {
          location.href = anchor;
        }
      });
    }

This work with Firefox and mobile(chrome) but does not work with desktop(chrome).
chrome(desktop) save the last scroll position and scroll to it after refresh .
.
Edit:
I add history.scrollRestoration = "manual"; inside <script></script>
Then it work with chrome(desktop)

@Mohammad-Alavi
Copy link

This is by far the cleanest solution that i have found and it works perfectly.

      methods: {
        goto(refName) {
            var element = this.$refs[refName];
          element.scrollIntoView();
        }
      }

If you wanted to get fancy and make the scroll smooth, you can even add the following:

element.scrollIntoView({ behavior: 'smooth' });

@posva posva added the fixed on 4.x This issue has been already fixed on the v4 but exists in v3 label May 26, 2020
@ryanklarhoelter
Copy link
Contributor

ryanklarhoelter commented Jul 5, 2021

@gspain @SilverDragon135 Is your solution still working for you? It seems that something has changed since your post. At least it's not working for me. Same behavior.

ryanklarhoelter pushed a commit to ryanklarhoelter/vue-router that referenced this issue Jul 12, 2021
@HenrijsS
Copy link

It's been almost 4 years. How has this not been fixed yet???

@saadeghi
Copy link

saadeghi commented Oct 8, 2021

My grandfather had said that one day this bug would be fixed but no one believed him. Here we are after ages...

@bluetomlee
Copy link

Already 2023, Any further progress??

@vuejs vuejs locked as spam and limited conversation to collaborators Jan 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
fixed on 4.x This issue has been already fixed on the v4 but exists in v3 has workaround improvement
Projects
None yet
Development

Successfully merging a pull request may close this issue.