DEV Community

loading...

Nuxt.js Smooth Scrolling with Hash Links

dimer191996 profile image Dimer Bwimba ・2 min read

As of Nuxt.js release 1.4.2, the default scroll behavior does not work as expected when using element ID's as hash links in routes (example: about-us/#john).

For reference: Nuxt.js Default Scroll Behavior

When navigated to directly, meaning the user immediately enters through the hash appended route, the browser handles the scroll targeting to the element with the matching ID. This is the expected behavior and works perfectly on initial page loads for completely static pages.

Once the site has loaded, however, the site operates as a single page application (SPA) and the browser stops responding to route changes as those are now handled by the vue-router. This allows for quicker page loads and navigation within the site is more controllable, but the browser no longer handles scrolling to focus on element IDs specified in hash appended routes, which has potential for breaking sites utilizing this functionality.

The solution is to override the default router.scrollBehavior method from within the nuxt.config.js configuration object.

module.exports = {
  /*
  ** Router configuration
  */
  router: {
    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 }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This configuration override solves two problems. First, it applies smooth to the window.scrollTo action to allow the browser to handle smooth scrolling to the proper element if available.

window.scrollTo({ top: el.offsetTop, behavior: 'smooth' })
Enter fullscreen mode Exit fullscreen mode

Second, it checks for the existence of the element several times (50 to be exact) over the course of several seconds. The default scroll behavior expects the content to be loaded by the time the scroll action is called, but default Nuxt sites load the framework and start initial render before the full content is loaded from the server or CMS. The default script will give up after the first miss, causing the page to stay focused at the top. Rather than giving up after the first failed attempt, this script continues to search the DOM for the expected element every 100 milliseconds for 5 seconds (approximately). There is in theory more programmatic ways to determine when the content has finished loading, but the cost of complexity likely outweighs the fringe cases this code does not cover.

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)
  })
}
Enter fullscreen mode Exit fullscreen mode

This approach works well in both Nuxt modes and gives a uniform user experience regardless of whether the SPA has finished loading or not.

Discussion (8)

Collapse
katieadamsdev profile image
Katie Adams

Hey, great work on this article. I'm working on a Nuxt project atm and this will be super useful!

Collapse
dimer191996 profile image
Dimer Bwimba Author

wow you can't image how awesome I feel when you say that. Send me your GitHub so i can take a look at your project. :)

Collapse
katieadamsdev profile image
Katie Adams

I'm glad it was appreciated! It's a work project so unfortunately we don't have a public repo for obvious reasons. I'll see if I can find a personal project to use it on some time though!

Collapse
ed3899 profile image
Edward Casanova • Edited

You are f"#$"n smart! This was killing me for hours. Can't believe they still haven't internally fix this issue. Specially with static pages and hash navigation, this is a nuisance.

Thx man

Collapse
dimer191996 profile image
Dimer Bwimba Author

Same here!, I don't really know what they're waiting for . Thanks To you man for reading it !

Collapse
dimer191996 profile image
Dimer Bwimba Author

happy coding

Some comments have been hidden by the post's author - find out more

Forem Open with the Forem app