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