How to handle anchors (bookmarks) with Vue Router?

风流意气都作罢 提交于 2020-08-26 19:58:02

问题


I'm looking for a smart way to handle in-page anchors with Vue Router. Consider the following:

<router-link to="#app">Apply Now</router-link>
<!-- some HTML markup in between... -->
<div id="app">...</div>

The "scroll to anchor" behavior described in the docs works fine except:

  • When you click on the anchor, it brings you down to the div id="app". Now scroll away from the div back to the anchor and try clicking it again -- this time you will not jump down to the div. In fact, the anchor will retain the class router-link-active and the URL will still contain the hash /#app;
  • With the steps above, if you refresh the page (the URL will still contain the hash) and click on the anchor, nothing will happen either.

This is very unfortunate from the UX perspective because a potential customer has to manually scroll all the way down again to reach the application section.

I was wondering if Vue Router covers this situation. For reference, here's my router:

export default new VueRouter({
    routes,
    mode: 'history',
    scrollBehavior(to, from, savedPosition) {
        if (to.hash) {
            return { selector: to.hash }
        } else if (savedPosition) {
            return savedPosition;
        } else {
            return { x: 0, y: 0 }
        }
    }
})

回答1:


I haven't found anything in the resources to solve your issue but you could utitlize the $route.hash in your mounted hook of the component that holds your <router-view></router-view> to solve the refresh issue.

<script>
export default {
  name: 'app',
  mounted: function()
  {
    // From testing, without a brief timeout, it won't work.
    setTimeout(() => this.scrollFix(this.$route.hash), 1);
  },
  methods: {
    scrollFix: function(hashbang)
    {
      location.hash = hashbang;
    }
  }
}
</script>

Then to solve the issue of second clicks you could use the native modifier and bind to your <router-link></router-link>. It's a fairly manual process but will work.

<router-link to="#scroll" @click.native="scrollFix('#scroll')">Scroll</router-link>

There may also be something you could do with the router's afterEach method but haven't figured that out yet.




回答2:


Possible solution which is more resusable IMO:

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')

Within a template you could do something like:

<a @click="this.$router.options.pushWithAnchor('home', '#fee-calculator-section')"></a>

Sadly you cant use a scroll offset though




回答3:


If you're already on the route with the hash, you can just set it to scroll to the target.

(also note scrollBehavior() method in your router won't get called if you're already on the route you're trying to go to).

export default {
  methods: {
    anchorHashCheck() {
      if (window.location.hash === this.$route.hash) {
        const el = document.getElementById(this.$route.hash.slice(1))
        if (el) {
          window.scrollTo(0, el.offsetTop)
        }
      }
    },
  },
  mounted() {
    this.anchorHashCheck()
  },
}

Then add a @click.native to listen to events on the anchor in your <router-link>,

<router-link :to="{hash: '#some-link'}" @click.native="anchorHashCheck">
  Some link
</router-link>



回答4:


I used this solution:

<router-link to="/about" @click.native="scrollFix('#team')" >The team</router-link>

And this:

methods: {
    scrollFix: function(hash) {
      setTimeout(() => $('html, body').animate({
      scrollTop: $(hash).offset().top
      }, 1000), 1)
    }
  }



回答5:


I realize you asked about anchors and you have an answer already. However, the below function should work for both anchors and regular links. It allows you to scroll to the position of the first instance of the Component a Route has been matched to. I wrote it to see if I could by-pass using a hash will retaining anchor-style scrolling.

scrollBehavior(to, from, savedPosition) {
if (to.matched) {
  const children = to.matched[1].parent.instances.default.$children;
  for (let i = 0; i < children.length; i++) {
    let child = children[i];
    if (child.$options._componentTag === to.matched[1].components.default.name) {
      return {
        x: child.$el.offsetLeft,
        y: child.$el.offsetTop
      };
    }
  }
}
return {
  x: 0,
  y: 0
};
}

The reason I'm using parent.instances is because the to.matched[1].instances value is empty. It's not the most elegant solution, though it might help someone else out there.

Note: This only works when you want to scroll the first instance of a Component.



来源:https://stackoverflow.com/questions/45201014/how-to-handle-anchors-bookmarks-with-vue-router

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!