Add class to element on hover in Vue, without using data

Tags: ,



In a Vue-component, I have a menu like this:

<ul class="menu-outer-wrapper">
  <li><a href="/foo-1">Foo 1</a></li>
  <li class="has-children">
    <a href="/foo-2">Foo 2</a>
    <ul>
      <li><a href="/child-1">Child 1</a></li>
      <li><a href="/child-2">Child 2</a></li>
      <li><a href="/child-3">Child 3</a></li>
    </ul>
  </li>
  <li><a href="/foo-5">Foo 5</a></li>
  <li class="has-children">
    <a href="/foo-6">Foo 6</a>
    <ul>
      <li><a href="/child-1">Child 1</a></li>
      <li><a href="/child-2">Child 2</a></li>
    </ul>
  </li>
  <li><a href="/foo-7">Foo 7</a></li>
  <li><a href="/foo-8">Foo 8</a></li>
</ul>

And I would like to add the class hovered to the li.has-children-elements upon hover (mouseenter) (to be able to make some nicer animations for the children of that dropdown. And remove that class on mouseleave.

I know that there are options to do this with pure CSS, – but controlling delays and soft fade-in’s are a pain (and become very messy very fast, without adding some classes).

I imagined doing something like this:

  ...
  mounted(){
    let liWithChildren = document.querySelectorAll( '.menu-outer-wrapper > li.has-children' );
    liWithChildren.forEach( (event, window) => {
      // Somehow add the class to the hovered element here.
      // But I don't know how. Or if it's a good/bad idea (performance-wise). 
    } 
  }

But is that the way to go? And can I do it without using data (since the menu is dynamically generated by a CMS-system.


Update 1

I’m trying to keep my markdown readable. So I would like to avoid something like this:

<ul class="menu-outer-wrapper">
  <li :class="[ { 'hovered' : someVar } ]">
    <a href="/foo-1">Foo 1</a>
  </li>
  <li :class="[ { 'hovered' : someVar }, 'has-children' ]">
    <a href="/foo-2">Foo 2</a>
    <ul>
      <li><a href="/child-1">Child 1</a></li>
      <li><a href="/child-2">Child 2</a></li>
      <li><a href="/child-3">Child 3</a></li>
    </ul>
  </li>
  <li :class="[ { 'hovered' : someVar } ]">
    <a href="/foo-3">Foo 2</a>
  </li>
...
...
...

Both since it won’t gel with the dynamically generated menu. And also since it add a lot of noise to the markdown.


Update 2

I simplified the example, to make it easier to digest. But due to the comments I figured I would elaborate on the dynamic generated menu. I’m making it something like this:

<nav id="secondary-menu" v-if="secondaryMenu">
  <ul>
    <li
      :class="[ { 'has-children': r.children } ]"
      v-for="(r, r_key, r_index) in secondaryMenu">
      <a :href="r.url" :title="r.title">
        {{ r.title }}
      </a>
      <ul class="children" v-if="r.children">
        <li v-for="(c1, c1_key, c1_index) in r.children">
          <a :href="c1.url" :title="c1.title">
            {{ c1.title }}
          </a>
        </li>
      </ul>
    </li>
  </ul>
</nav>

Answer

You just need the @mouseenter and @mouseleave events. All you need to do is listen for the appropriate events on all list-items that could have children, then perform your class addition (or removal) if the target element has the class of “has-children”. Here’s how I would do it:

<template>
  <nav id="secondary-menu" v-if="secondaryMenu">
    <ul>
      <li
        :class="[{ 'has-children': r.children }]"
        v-for="(r, r_key, r_index) in secondaryMenu"
        :key="`${r_key}-${r_index}`"
        @mouseenter="addClass"
        @mouseleave="removeClass"
      >
        <a :href="r.url" :title="r.title">
          {{ r.title }}
        </a>
        <ul class="children" v-if="r.children">
          <li
            v-for="(c1, c1_key, c1_index) in r.children"
            :key="`${c1_key}-${c1_index}`"
          >
            <a :href="c1.url" :title="c1.title">
              {{ c1.title }}
            </a>
          </li>
        </ul>
      </li>
    </ul>
  </nav>
</template>
<script>
export default {
  name: "HoverNav",
  props: {
    secondaryMenu: {
      type: Array,
      required: true,
    },
  },
  methods: {
    addClass: function (e) {
      if (e.target.classList.contains("has-children")) {
        e.target.classList.add("hovered");
      }
    },
    removeClass: function (e) {
      if (e.target.classList.contains("has-children")) {
        e.target.classList.remove("hovered");
      }
    },
  },
};
</script>

Here’s a very unaesthetic sandbox of this in action. Let me know if this works for you 🙂

https://codesandbox.io/s/headless-brook-ysq97?file=/src/components/HoverNav.vue:0-1169



Source: stackoverflow