d-_-b
d-_-b

Reputation: 23211

Prevent scrolling to top when vue router changes query params

How can I prevent the page from scrolling to the top when I change the route's query param (and the view's props)?

I've tried the following with no luck:

Attempt 1 - Route's component

When I make the timeout an arbitrarily large number (1 second) then it scrolls back down after some delay.

// in my route's component
props: {...},
watch: {
  $route(to, from) {
      let y = window.scrollY;
      this.$nextTick(() => {
        setTimeout(() => {
          console.log(`scrolling to ${y}`);
          window.scrollTo(0, y);
        }, 0);
      });
    }
}

Attempt 2 - $router's scrollBehavior

This logs the correct y value, but doesn't maintain the old position.

scrollBehavior(to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition;
    }
    if (from.path !== to.path) {
      return { x: 0, y: 0 };
    }

    let existing = {
      x: window.scrollX,
      y: window.scrollY
    };
    console.log(`Existing scroll`, existing);
    return new Promise(resolve => {
      setTimeout(() => {
        resolve(existing);
      }, 0);
    });

  },

Upvotes: 20

Views: 11227

Answers (5)

TitanFighter
TitanFighter

Reputation: 5114

Remarks to other answers:

1. hashbag: true,. Neither hashbag nor hashbang I can find in the current version of vue-router docs. Probably this is an old attribute.

2. if (to.params.savePosition) return {} + this.$router.push({ query: query, params: { savePosition: true } })

No need to use additional param, as savePosition as a fix of this issue.


My answer with comments:

const router = new VueRouter({
  mode: 'history', // or 'hash'
  routes,
  scrollBehavior (to, from, savedPosition) {
    // Exists when Browser's back/forward pressed
    if (savedPosition) {
      return savedPosition
    // For anchors
    } else if (to.hash) {
      return { selector: to.hash }
    // By changing queries we are still in the same component, so "from.path" === "to.path" (new query changes just "to.fullPath", but not "to.path").
    } else if (from.path === to.path) {
      return {}
    }

    // Scroll to top
    return { x: 0, y: 0 }
  }
})

Upvotes: 8

Risyad
Risyad

Reputation: 317

I just found an answer for this. Here's my code. The default behaviour will scroll to top, unless you pass a custom params, which will be ignored by the router if a path is provided (https://router.vuejs.org/guide/essentials/navigation.html).

scrollBehavior (to, from, savedPosition) {
    // savedPosition is only available for popstate navigations.
    if (savedPosition) return savedPosition

    // if the returned position is falsy or an empty object,
    // will retain current scroll position.
    if (to.params.savePosition) return {}

    // scroll to anchor by returning the selector
    if (to.hash) {
      let position = {selector: to.hash}

      // specify offset of the element
      // if (to.hash === '#anchor2') {
      //   position.offset = { y: 100 }
      // }
      return position
    }

    // scroll to top by default
    return {x: 0, y: 0}
  }

If you return a falsy or an empty object, Vue router will return the original position. Then I will just pass a custom params which I use by the name of 'savePosition' to params.

this.$router.push({ query: query, params: { savePosition: true } })

That way, your router by default will scroll to top unless you pass savePosition to params, or if you pass a hash.

Upvotes: 21

Akrd Kmnc
Akrd Kmnc

Reputation: 112

If you don't want to scroll to top when your params change. You can check the to and next that path is not equal.

scrollBehavior(to, from, savedPosition) {
   if (to.path != from.path) {
      return { x: 0, y: 0 };
   }
}

Upvotes: 4

Abdelillah Aissani
Abdelillah Aissani

Reputation: 3108

try using navigation guards

// in your router.js or whatever your router config is
var clientY = 0
router.beforeEach((next) => {
  clientY = window.scrollY
  next()
})

router.afterEach(() => {
  window.scrollTo(0,clientY)
})

Upvotes: 2

IonV
IonV

Reputation: 442

Had the similar issue, worked for me only when added the mode: history and hashbag: true. This is how looks settings for VueRouter:

mode: 'history',
hashbag: true,
scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
        return savedPosition
    } else {
        return { x: 0, y: 0 }
    }
},

Hope it helps.

Upvotes: 1

Related Questions