-1

In my Nuxt.js 3 project, I want to implement single-page navigation. And I followed following articles but it didn't work. any suggestions?

Sathya Molagoda
  • 531
  • 1
  • 6
  • 17
  • 1
    Hi, what is not working exactly on your side? Did you tried that one? https://stackoverflow.com/a/73893669/8816585 And this one? https://stackoverflow.com/a/73069885/8816585 – kissu Oct 03 '22 at 07:32
  • when I click my nav item it will not navigation to particular anchortag position. `{{ menuItem.label }}` – Sathya Molagoda Oct 03 '22 at 07:37
  • Did you checked my given links? Also, please add more details regarding your setup, right now it's still quite vague. – kissu Oct 03 '22 at 07:42
  • yes this ( https://stackoverflow.com/questions/73069572/nuxt-js-3-and-on-site-anchor-navigation/73069885#73069885) worked. thanks – Sathya Molagoda Oct 03 '22 at 07:44
  • But do you know how to add a smooth transition for this? – Sathya Molagoda Oct 03 '22 at 07:44

5 Answers5

2

The correct way to do it in Nuxt.js 3 is to create the "router.scrollBehaviour.js" file in the plugins/ directory. Its content should be

import { defineNuxtPlugin } from "#app";

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.$router.options.scrollBehavior = async (to, from, savedPosition) => {
    if (savedPosition) {
      return savedPosition;
    }

    const findEl = async (hash, x = 0) => {
      return (
        document.querySelector(hash) ||
        new Promise((resolve) => {
          if (x > 0) {
            return resolve(document.querySelector("#app"));
          }
          setTimeout(() => {
            resolve(findEl(hash, 1));
          }, 300);
        })
      );
    };

    if (to.hash) {
      const el = await findEl(to.hash);

      if ("scrollBehavior" in document.documentElement.style) {
        console.log("hash path hit scroll to");
        return window.scrollTo({ top: el.offsetTop, behavior: "smooth" });
      } else {
        return window.scrollTo(0, el.offsetTop);
      }
    }
    return { left: 0, top: 0, behaviour: "smooth" };
  };
})
Cyril N.
  • 38,875
  • 36
  • 142
  • 243
Sathya Molagoda
  • 531
  • 1
  • 6
  • 17
  • I do not recommend using querySelectors but I guess that it's working for you right now. – kissu Oct 03 '22 at 08:38
  • @kissu Could you elaborate why you don't recommend using querySelectors? – Cyril N. Aug 19 '23 at 09:43
  • @CyrilN. Vue is based on working with state, while query selectors have their own DOM manipulation kind of approach, mixing both will not be friendly if you want to pass the data back and forth. You can make something reactive with a ref tho, probably the best approach overall. Or even VueUse composables. – kissu Aug 19 '23 at 11:51
  • @kissu in the above case, the dom is used to retrieve information, not too manipulate it, so it shouldn't pose any issues. But I agree with you that it shouldn't be used for dom manipulation. – Cyril N. Aug 19 '23 at 12:24
  • @CyrilN. You can just stick to a template ref in the same manner, no need to switch context. – kissu Aug 19 '23 at 15:03
1

In Nuxt.js 3 you can do this without a plugin. Simply place a "app/router.options.ts" within the root of your project and add following code to it:



    import type { RouterConfig } from "@nuxt/schema";
    
    export default {
      scrollBehavior(to, from, savedPosition) {
        if(savedPosition)
          return savedPosition;
    
        if (to.hash && to.path == from.path) {
           const el = document.querySelector(to.hash);
        return { top: el.offsetTop, left: 0, behavior: "smooth" };
        }
    
        return {
          top: 0,
          left: 0
        }
      }
    };

0

i prefer this:

plugins/scroll-behavior.client.ts

import { useRouter } from "vue-router";

export default async function () {
  if (typeof window !== "undefined") {
    const router = useRouter();

    router.afterEach(async (to, from) => {
      if (to.hash) {
        const targetElement = document.querySelector(to.hash);

        if (targetElement) {
          await targetElement.scrollIntoView({
            behavior: "smooth",
            block: "start",
            inline: "nearest",
          });
          return;
        }
      }

      window.scrollTo({ top: 0, behavior: "smooth" });
    });
  }
}

in the nuxt.config.ts add this:

  plugins: [
    { src: '~/plugins/scroll-behavior.client.ts', mode: 'client' }
  ]

then, in your component, reference something with an id, let's say id="about" heres a button on the page that will smoothly put the about hash in display.

<nuxt-link :to="{ hash: '#about' }" class="btn btn-lg btn-warning mx-1">Learn more</nuxt-link>
Kaiser
  • 95
  • 2
  • 8
0

Define pulgin file for Nuxt3 in plugins/scroll-behavior.client.ts like Kaiser mentioned, But I had to modify it to work for Nuxt3. The main difference that you should use beforeEach to call your animation before the transition actually happens. Otherwise you end up trying to animate position that is alredy there. The delay promise makes up transition time, if you make it shorter, transition would be faster. This works for single page just fine, but may require upgrades for navigating anchors between pages.

      export default defineNuxtPlugin(async function () {
          if (typeof window !== "undefined") {
        
            const router = useRouter();
        
            router.beforeEach((to) => {
              if (to.hash) {
                const targetElement = document.querySelector(to.hash);
                if (targetElement) {
                  targetElement.scrollIntoView({
                      behavior: "smooth"
                  })
                  const p:Promise<boolean> = new Promise((resolve)=>{
                    setTimeout(()=>{
                      resolve(true);
                    },1000)
                  })
                  return p
                }
              }
        
              window.scrollTo({ top: 0, behavior: "smooth" });
            });
          }
        });

And set up the plugin in nuxt.config.ts like mentioned before:

  plugins: [
    { src: '~/plugins/scroll-behavior.client.ts', mode: 'client' }
  ]

Then you can use your navigation with hashes like this:

<NuxtLink :to="{path: '/', hash: '#anchor1'}">
          <div class="button">Link 1</div>
</NuxtLink>

...

<div id="anchor1"> Target content block </div>
0

Using a plugin isn't the way to do it in Nuxt 3 - have a look right here: https://github.com/nuxt/nuxt/issues/22663