Home > Net >  Vue Router not scolling to correct hash location
Vue Router not scolling to correct hash location

Time:02-23

When clicking a link on the app, the Router does not scroll to the correct hash location given. But it DOES scoll. The scroll is always about 1 element too high on the page. For example, if my page is this:

<template>
    <div id="div1">Div1</div>
    <div id="div2">Div2</div>
    <div id="div3">Div3</div>
    <div id="div4">Div4</div>
</template>

Then if I click a link that goes to hash "div3" it will scroll me so the top of div2. Only a very few select elements are scrolled to correctly. Is there a reason for this? Setting page margins?

Router code:

const router = new VueRouter({
  routes,
  mode: 'history',
  base: "/",
  scrollBehavior (to, from, savedPosition) {
    if (to.hash) {
        return {
          selector: to.hash,
          behavior: 'smooth',
        }
    } else {
        return { x: 0, y: 0 }
    }
  }
})

Example of code that calls the hash routing:

if (item.title == "Mission") {
    router.push({name: 'Explore', hash: '#mission-statement'});
} else if (item.title == "Our Story") {
    router.push({name: 'Explore', hash: '#our-story-container'});
} else if (item.title == "Shared principles") {
    router.push({name: 'Explore', hash: '#shared-principles-container'});
} else if (item.title == "Volunteer Opportunities") {
    router.push({name: 'Explore', hash: '#volunteer-container'});
} else if (item.title == "Gallery") {
    router.push({name: 'Explore', hash: '#galleries'});
} else if (item.title == "Living") {
    router.push({name: 'Explore', hash: '#living-container'});
} else if (item.title == "Contact Us") {
    router.push({name: 'Explore', hash: '#contact-us-container'});
} else {
    router.push("/explore")
}

SOLUTION:

With the help of IVO GELOV's answer I was able to update the scroll behavior function with the code below and all works as expected.

scrollBehavior (to, from, savedPosition) {
    let position = {}
    if (to.hash) {
        position = {
          selector: to.hash,
          offset: { x: 0, y: 100 },
          behavior: 'smooth',
        }
    } else {
        position = { x: 0, y: 0 }
    }
    return new Promise((resolve) => {
      setTimeout(() => {
        resolve(position)
      }, 100)
    })
  }

CodePudding user response:

Nuxt.js is using the following code in its ./nuxt/router.js - perhaps you can adopt it to your usecase:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router) 

if (process.client) {
  window.history.scrollRestoration = 'manual'
}
const scrollBehavior = function (to, from, savedPosition) {
  // if the returned position is falsy or an empty object,
  // will retain current scroll position.
  let position = false

  // if no children detected
  if (to.matched.length < 2) {
    // scroll to the top of the page
    position = { x: 0, y: 0 }
  } else if (to.matched.some((r) => r.components.default.options.scrollToTop)) {
    // if one of the children has scrollToTop option set to true
    position = { x: 0, y: 0 }
  }

  // savedPosition is only available for popstate navigations (back button)
  if (savedPosition) {
    position = savedPosition
  }

  return new Promise(resolve => {
    // wait for the out transition to complete (if necessary)
    window.$nuxt.$once('triggerScroll', () => {
      // coords will be used if no selector is provided,
      // or if the selector didn't match any element.
      if (to.hash) {
        let hash = to.hash
        // CSS.escape() is not supported with IE and Edge.
        if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined') {
          hash = '#'   window.CSS.escape(hash.substr(1))
        }
        try {
          if (document.querySelector(hash)) {
            // scroll to anchor by returning the selector
            position = { selector: hash }
          }
        } catch (e) {
          console.warn('Failed to save scroll position. Please add CSS.escape() polyfill (https://github.com/mathiasbynens/CSS.escape).')
        }
      }
      resolve(position)
    })
  })
}

export function createRouter () {
  return new Router({
    mode: 'history',
    base: '/',
    linkActiveClass: 'nuxt-link-active',
    linkExactActiveClass: 'nuxt-link-exact-active',
    scrollBehavior,
    routes: [ .... ],
    fallback: false
  })
}

An example implementation (taken from https://dev.to/dimer191996/nuxt-js-smooth-scrolling-with-hash-links-94a) - you may also need to set window.history.scrollRestoration = 'manual' if your <router-view> is wrapped inside a <transition>:

scrollBehavior: async (to, from, savedPosition) => {
      if (savedPosition) {
        return savedPosition
      }

      const findEl = async (hash, x) => {
        return document.querySelector(hash) ||
          new Promise((resolve, reject) => {
            if (x > 50) {
              return resolve()
            }
            setTimeout(() => { resolve(findEl(hash,   x || 1)) }, 100)
          })
      }

      if (to.hash) {
        let el = await findEl(to.hash)
        if ('scrollBehavior' in document.documentElement.style) {
          return window.scrollTo({ top: el.offsetTop, behavior: 'smooth' })
        } else {
          return window.scrollTo(0, el.offsetTop)
        }
      }

      return { x: 0, y: 0 }
    }
  • Related