Wskaźnik aktualnej zakładki

0

Chciałem stworzyć jakiś w miarę ciekawy navbar z wskaźnikiem aktualnie otwartej karty(chodzi o ten pasek pod linkiem), aktualnie wygląda to tak, jak niżej:
screenshot-20221219183825.png
Problem polega na tym, że przy pierwszym załadowaniu strony wskaźnik się nie aktualizuje,

kod komponentu Navbar:

<template>
  <nav>
    <RouterLink to="/"><img :src="logo" alt="Logo" /></RouterLink>
    <ul class="links" @mouseleave="fixIndicator" @resize="fixIndicator">
      <li @mouseenter="moveIndicator" :class="{ active: route.name == 'home' }">
        <RouterLink to="/">Home</RouterLink>
      </li>
      <li @mouseenter="moveIndicator" :class="{ active: route.name == 'about' }">
        <RouterLink to="/about">About</RouterLink>
      </li>
      <li @mouseenter="moveIndicator" :class="{ active: route.name == 'team' }">
        <RouterLink to="/team">Our team</RouterLink>
      </li>
      <li @mouseenter="moveIndicator" :class="{ active: route.name == 'demo' }">
        <RouterLink to="/demo">Demo</RouterLink>
      </li>
      <span id="indicator"></span>
    </ul>
  </nav>
</template>
<script>
import { animate } from "motion";
import { RouterLink, useRoute } from "vue-router";
import logo from "../assets/img/logo.png";
export default {
  name: "Navbar",
  data() {
    return {
      logo: logo,
    };
  },
  methods: {
    moveIndicator(e) {
      const node = e.target;
      const nodeLeft = node.offsetLeft;
      const nodeWidth = node.offsetWidth;
      animate("#indicator", { width: `${nodeWidth}px`, left: `${nodeLeft}px` }, { duration: 0.2, easing: "ease-in-out" });
    },
    fixIndicator() {
      const node = document.querySelector("nav .links li.active");
      if (node == null) {
        console.error("Active link not found.");
        return;
      }
      const nodeLeft = node.offsetLeft;
      const nodeWidth = node.offsetWidth;
      animate("#indicator", { width: `${nodeWidth}px`, left: `${nodeLeft}px` }, { duration: 0.2, easing: "ease-in-out" });
    },
  },
  computed: { route: () => useRoute() },
};
</script>

Próbowałem wywoływania metody fixIterator podczas onMount, ale wyrzucało błąd(node byl undefined).
Pewnie jest do tego jakiś event, ale nie mam pojęcia jaki.

Z góry dzięki.

0

Próbowałeś zrezygnować z tych wszystkich funkcji i całą animacje przenieść do css?

EDIT:

Zamiast onMount, spróbowałbym mounted

https://vuejs.org/guide/essentials/lifecycle.html

EDIT 2:

Metodę querySelector można zastąpić przez ref

https://vuejs.org/guide/essentials/template-refs.html

ewentualnie można skorzystać z this.$el

https://vuejs.org/api/component-instance.html#el

EDIT 3:

Taki zapis jest błędny, ponieważ <ul> nie może posiadać bezpośrednio znacznika <span> (chociaż taka informacja w żaden sposób nie wpływa na Twój problem) :P

<ul>
  <li>...</li>

  <span></span>
</ul>
1

Przykład online: https://stackblitz.com/edit/vue-lt9dbm?file=src%2FApp.vue,src%2Fmain.js

Po stronie JavaScriptu zapisuje jedynie szerokość bieżącego linku, oraz jego pozycję, pozostała część jest w CSS

<template>
  <ul @mouseleave="mouseleave">
    <li
      v-for="(link, index) in links"
      @mouseenter="mouseenter"
      :class="{ active: index === 2 }"
      ref="navLinks"
    >
      <span>{{ link.text }}</span>
    </li>
  </ul>
</template>

<script>
export default {
  name: 'App',
  
  data() {
    return {
      links: [
        { text: 'Strona główna' },
        { text: 'O nas' },
        { text: 'Link z bardzo, bardzo długą treścią' },
        { text: 'Rejestracja' },
      ],
    };
  },
  
  computed: {
    activeLink() {
      return this.$refs.navLinks.find((el) => el.classList.contains('active'));
    },
  },
  
  methods: {
    mouseleave(event) {
      event.target.style.removeProperty('--line-pos-x');
      event.target.style.removeProperty('--line-width');
    },

    mouseenter(event) {
      event.target.parentElement.style.setProperty(
        '--line-width',
        `${event.target.firstElementChild.offsetWidth}px`
      );

      event.target.parentElement.style.setProperty(
        '--line-pos-x',
        `${event.target.offsetLeft - this.activeLink.offsetLeft}px`
      );
    },
  },
};
</script>

<style>
ul {
  display: flex;
  width: fit-content;
  list-style: none;
  margin: 0;
  padding: 0;
}

ul li {
  padding: 10px 30px;
  text-align: center;
}

ul li span {
  position: relative;
}

ul li.active span::before {
  position: absolute;
  left: 0;
  bottom: -5px;
  width: var(--line-width, 100%);
  height: 1px;
  background: red;
  transform: translateX(var(--line-pos-x));
  transition: transform 0.35s ease, width 0.35s ease;
  content: '';
}
</style>

i można powiedzieć, że wygląda dość podobnie jak na Twoim filmiku.

1 użytkowników online, w tym zalogowanych: 0, gości: 1