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>
Advertisement
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