vuejs / vue-router

🚦 The official router for Vue 2
http://v3.router.vuejs.org/
MIT License
18.99k stars 5.06k forks source link

Navigation without history API should preserve scroll positions #1967

Closed ahus1 closed 6 years ago

ahus1 commented 6 years ago

What problem does this feature solve?

Using vue router without history API makes it easier for a backend to serve just an index.html as only hashes are added to the URL for the different routes. This is the reason we haven't switched to html5 history mode.

But using the hashes navigation doesn't preserve the scroll position. It doesn't even call the scrollBehavior methods that would make an implementation easier.

A working solution for us (and probably most other other users) would be to save the scroll position once-per-route in pure javascript and provide these values once you enter the route again.

I open this issue because I think this will help others. If you consider it out of scope for vue-router, consider closing it right away.

What does the proposed API look like?

No additional API would be provided. ScrollBehavior would be called even if not in HTML5 mode. Scroll positions would be saved in a once-per-route in pure JavaScript inside vue-router.

For us the following code works for forward and backward javascript navigation. It's part of the following open source app - https://latest.dukecon.org/pwa/javaland/2018/ (full source available here https://github.com/dukecon/dukecon_pwa/blob/develop/src/main.js)

  // for each page, store the scroll position
  var positionStore = {}

  /* record the scrolling on current route (works better as back-navigation scrolls to different position,
  and this would otherwise be recorded by beforeEach() */
  window.onscroll = function () {
    positionStore[app.$route.path] = {
      x: window.pageXOffset,
      y: window.pageYOffset
    }
  }

  // whenever the route changes, scroll to old position
  router.beforeEach((to, from, next) => {
    const oldPosition = positionStore[to.path]
    // restore position after next screen rendering
    app.$nextTick(() => {
      if (!oldPosition || (oldPosition.y === 0 && oldPosition.x === 0)) {
        window.scrollTo(0, 0)
      } else {
        // I found that rendering of the screen might take a little bit more time,
        // therefore wait a bit if we don't scroll to the top
        window.setTimeout(function () {
          window.scrollTo(oldPosition.x, oldPosition.y)
        }, 50)
      }
    })
    next()
  })
posva commented 6 years ago

Using vue router without history API makes it easier for a backend to serve just an index.html as only hashes are added to the URL for the different routes. This is the reason we haven't switched to html5 history mode.

Are you aware of this page about configuring servers to always serve the index.html page?

But using the hashes navigation doesn't preserve the scroll position. It doesn't even call the scrollBehavior methods that would make an implementation easier.

scrollBehavior actually works with hash mode since #1662, if it's not, could you provide a repro please?

ahus1 commented 6 years ago

I wasn't aware of the change in 2.8.0 - sorry to bother you.

posva commented 6 years ago

No worries! It could have been a regression