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

Open
ParachuteCat opened this issue Aug 4, 2017 · 12 comments
Open

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

ParachuteCat opened this issue Aug 4, 2017 · 12 comments
Labels
Projects

Comments

@ParachuteCat
Copy link

@ParachuteCat ParachuteCat commented Aug 4, 2017

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)

@posva posva added the improvement label Aug 4, 2017
@svewag

This comment has been minimized.

Copy link

@svewag 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

This comment has been minimized.

Copy link

@speir-wang speir-wang commented Nov 20, 2017

@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

This comment has been minimized.

Copy link

@CharlesKumar CharlesKumar commented Nov 21, 2017

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

This comment has been minimized.

Copy link

@sem4phor 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

This comment has been minimized.

Copy link

@aldencolerain 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

This comment has been minimized.

Copy link

@gspain 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

This comment has been minimized.

Copy link

@ranaclyde ranaclyde commented Sep 7, 2018

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

This comment has been minimized.

Copy link

@SilverDragon135 SilverDragon135 commented Sep 16, 2018

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

@ajb413

This comment has been minimized.

Copy link

@ajb413 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

This comment has been minimized.

Copy link

@vinstah 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

@posva posva added this to Long term road (high prio, low complex) in Longterm Mar 26, 2019
@pkkid

This comment has been minimized.

Copy link

@pkkid 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

This comment has been minimized.

Copy link

@RobertDiebels 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Longterm
Long term road (high prio, low complex)
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.