1
votes

This is a follow up to #1458. I'm looking for some direction on how Nuxt expects this to be handled.

I have a menu. When I click on a nuxt-link in the menu, I want to have time to close the menu before the page transition happens. The thing is, I only want that to happen when you click on the nuxt-link in the menu, not every time I go to a certain route (as the previous issue described using a middlewear on the route).

So there are a few different ways to do this, and I'm curious what the "Nuxt" way is?

The way we currently do this, disable the nuxt-link and capture the click, then do a router.push().

<nuxt-link :to="path" event="disabled" @click.native="delayLoad"/>

// Methods
delayLoad(event) {
     this.$store.commit("CLOSE_MENU")

     setTimeout(()=>{
        this.$router.push(event.target.pathname)
    }, 2000)
}

Is this a good idea? I just always have an aversion to hijacking nuxt-link and browser navigation like this. It seems janky.

The other ideas we played with were using a query param on the nuxt-link, and then using that in a middlewear to delay the page transition. That seemed worse to me, because now my URL's have a query param in them that is used for an animation, seems like that is abusing query params. This also triggers the page loading progress bar, which isn't really the intent, it's to have a sequenced animation happen, then page load.

It seems to me that perhaps nuxt-link should have a delay prop, or perhaps the page transition config should allow for a delay (like it does with duration)?

2

2 Answers

2
votes

Since nuxt-link is essentially a wrapped version of vue's router-link, if you look at the documentation for that there is an event property that accepts string or string[], looking at it's source code here: https://github.com/vuejs/vue-router/blob/dev/src/components/link.js#L86 you can see it will register a listener for disabled in this instance. It may make more sense to pass an empty array so that no event listeners are registered, but that's at the cost of readability.

Otherwise, @click.native is the suggested way to handle custom click handlers for router-link (see: https://github.com/vuejs/vue-router/issues/800#issuecomment-254623582).

The only other concerns I can think of are what happens if you click 2 different links in rapid succession and what happens if you click more than once. May just want to add a variable to track if a link has been clicked to prevent firing setTimeout multiple times, which could navigate you from page A to B and then to C as all timeouts will fire if not canceled. Or maybe you want to only navigate to the 'last' link clicked, so if another link is clicked, you would cancel the earlier setTimeout. This is realistically an edge case that probably won't be an issue, but worth exploring.

Otherwise, IMO, looks good to me. This seems like the simplest way to implement this without having to create a custom component / plugin. I'm no expert, but is likely how I would implement this functionality as well. It would be nice to see a delay option though since I can see myself using that functionality as well with vuetify.

Another potential method would be to do your store commit in beforeTransition: https://nuxtjs.org/api/configuration-transition/ Though I'm not sure that there is access to the store there, so you might have to write a custom plugin for that as well. Again, seems more complicated than it's worth for a simple delayed animation. Simple, working code is sometimes the best solution, even if it's not the most extensible option.

See also: How can I transition between two nuxt pages, while first waiting on a child component transition/animation to finish? for another way of handling this.

2
votes

I wanted to do this as well and came up with the following solution. Using the new slots api you can more elegantly customise the nuxt-link behaviour:

<nuxt-link v-slot="{ route, href }" :to="path" custom>
  <a :href="href" @click.prevent="$emit('navigate', route)">
    <slot></slot>
  </a>
</nuxt-link>

This will make the link emit a navigate event with the route as a param. You then listen for this event wherever you include your menu component, like this:

<template>
  <transition
    name="fade"
    @after-leave="maybeNavigate"
  >
    <MainMenu
      v-if="menuIsVisible"
      @navigate="navigate"
    />
  </transition>
</template>

<script>
export default {
  data: () => ({
    menuIsVisible: false,
    navigateToOnMainMenuClose: null,
  }),
  methods: {
    navigate(route) {
      this.navigateToOnMainMenuClose = route
      this.menuIsVisible = false
    },
    maybeNavigate() {
      if (this.navigateToOnMainMenuClose) {
        this.$router.push(this.navigateToOnMainMenuClose)
        this.navigateToOnMainMenuClose = null
      }
    },
  },
}
</script>

Whenever you click a nav link in the menu, the route will be stored and the menu will close. After the menu out animation has finished, maybeNavigate() will push the stored route, if there is one. This removes the need for a setTimeout and if you manage to click multiple links in quick succession only the last one will be stored and navigated to.