在 Bulma 中导航到不同的 link 时,如何关闭悬停下拉菜单?
How do I make the hoverable dropdown close when navigating to a differerent link in Bulma?
HERE is a SANDBOX with the issue on it
- 我在导航栏中有一个可悬停的下拉菜单
- 当我转到另一个页面时,下拉菜单仍然打开
- 我已经在普通的 Bulma 中尝试过了,问题仍然存在
- 我正在 Nuxt.js 使用 SSR
- 我正在使用 nuxt-link / 相当于 Vue router-link 来导航到不同的页面。
这是我的 default.vue 文件
<template>
<div class="ch-container">
<header class="ch-header">
<nav
class="navbar is-fixed-top"
role="navigation"
aria-label="main navigation"
>
<div class="navbar-brand">
<nuxt-link class="navbar-item" to="/">
<img
alt="CH Logo"
src="https://i.imgur.com/v35Kfc9.png"
width="28"
height="28"
/>
</nuxt-link>
<a
role="button"
class="navbar-burger burger"
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<nuxt-link class="navbar-item" to="/news">
News
</nuxt-link>
<nuxt-link class="navbar-item" to="/resources">
Resources
</nuxt-link>
<nuxt-link class="navbar-item" to="/tickers">
Tickers
</nuxt-link>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
More
</a>
<div class="navbar-dropdown">
<a class="navbar-item">
FAQ
</a>
<nuxt-link class="navbar-item" to="/contact">
Contact
</nuxt-link>
<hr class="navbar-divider" />
<a class="navbar-item">
Feature Request
</a>
</div>
</div>
</div>
<div class="navbar-end">
<div class="navbar-item">
<a href="#">
<fa :icon="faMoon" />
</a>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link is-arrowless">
<fa :icon="faExclamationCircle" />
</a>
<div class="navbar-dropdown">
<a class="navbar-item">
No new notifications
</a>
</div>
</div>
<div class="navbar-item">
<div class="buttons">
<nuxt-link class="button is-primary" to="/signup">
<strong>Sign up</strong>
</nuxt-link>
<nuxt-link id="login" class="button is-light" to="/login">
Log in
</nuxt-link>
</div>
</div>
</div>
</div>
</nav>
</header>
<main class="ch-main">
<nuxt />
</main>
<footer class="ch-footer is-hidden-mobile">
<div class="level">
<div class="level-left">
<div class="level-item">
<a href="#">
<span class="icon">
<fa :icon="faFacebookSquare" />
</span>
</a>
<a href="#">
<span class="icon">
<fa :icon="faTwitterSquare" />
</span>
</a>
<a href="#">
<span class="icon">
<fa :icon="faRedditSquare" />
</span>
</a>
</div>
</div>
<div class="level-right">
<div class="level-item">
©ch, All Rights Reserved
</div>
<div class="level-item">
<nuxt-link to="/contact">Contact</nuxt-link>
</div>
<div class="level-item">
<nuxt-link to="/terms-of-service">Terms</nuxt-link>
</div>
<div class="level-item">
<nuxt-link to="/privacy-policy">Privacy</nuxt-link>
</div>
</div>
</div>
</footer>
</div>
</template>
<script>
import {
faFacebookSquare,
faTwitterSquare,
faRedditSquare,
} from '@fortawesome/free-brands-svg-icons'
import { faMoon, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'
export default {
computed: {
faFacebookSquare() {
return faFacebookSquare
},
faTwitterSquare() {
return faTwitterSquare
},
faRedditSquare() {
return faRedditSquare
},
faMoon() {
return faMoon
},
faExclamationCircle() {
return faExclamationCircle
},
},
mounted() {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(
document.querySelectorAll('.navbar-burger'),
0
)
// Check if there are any navbar burgers
if ($navbarBurgers.length > 0) {
// Add a click event on each of them
$navbarBurgers.forEach((el) => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target
const $target = document.getElementById(target)
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active')
$target.classList.toggle('is-active')
})
})
}
},
}
</script>
<style></style>
这是说明问题的 GIF
移动到其他页面后如何关闭下拉菜单?
好吧,整个事情变得复杂了,因为悬停是由 css 触发的,因此当鼠标悬停在下拉菜单上时总是可以看到它。你必须覆盖这个状态并用 vue 事件解决它。我们还必须在路线上放置一个观察者来重置状态。
<template>
<div class="container">
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<!-- <a class="navbar-item" href="https://bulma.io"> -->
<img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">
<a
role="button"
class="navbar-burger burger"
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item">Home</a>
<a class="navbar-item">Documentation</a>
<div
@mouseover="toggleDropdown(true)"
@mouseleave="toggleDropdown(false)"
class="navbar-item has-dropdown is-hoverable"
>
<a class="navbar-link">More</a>
<div class="navbar-dropdown" :style="{display: showDropdown ? 'block' : 'none' }">
<nuxt-link class="navbar-item" to="/about">About</nuxt-link>
<nuxt-link class="navbar-item" to="/jobs">Jobs</nuxt-link>
</div>
</div>
</div>
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<a class="button is-primary">
<strong>Sign up</strong>
</a>
<a class="button is-light">Log in</a>
</div>
</div>
</div>
</div>
</nav>
<Nuxt/>
</div>
</template>
<script>
export default {
data() {
return {
routeChange: false,
showDropdown: false
};
},
watch: {
$route() {
this.routeChange = true;
this.showDropdown = false;
}
},
methods: {
toggleDropdown(payload) {
if (this.showDropdown !== payload) this.routeChange = false;
if (!this.routeChange) {
this.showDropdown = payload;
}
}
}
};
</script>
这对我有用Bootstrap 5,在触摸设备上它使用点击,在有鼠标的设备上它使用悬停
<template>
<span
v-if="item"
class="primary-navigation-list-dropdown"
@mouseover="isTouchscreenDevice ? null : openDropdownMenu()"
@mouseleave="isTouchscreenDevice ? null : closeDropdownMenu()"
>
<nuxt-link
to="#"
@click.prevent.native="openDropdownMenu"
v-click-outside="closeDropdownMenu"
:title="item.title"
:class="[
item.cssClasses,
{ show: isDropdownMenuVisible }
]"
:id="`navbarDropdownMenuLink-${item.id}`"
:aria-expanded="[isDropdownMenuVisible ? true : false]"
class="
primary-navigation-list-dropdown__toggle
nav-link
dropdown-toggle"
aria-current="page"
role="button"
data-toggle="dropdown"
>
{{ item.label }}
</nuxt-link>
<ul
:class="{ show: isDropdownMenuVisible }"
:aria-labelledby="`navbarDropdownMenuLink-${item.id}`"
class="
primary-navigation-list-dropdown__menu
dropdown-menu-list
dropdown-menu"
>
<li
v-for="item in item.children" :key="item.id"
class="dropdown-menu-list__item"
>
<NavLink
:attributes="item"
class="dropdown-menu-list__link dropdown-item"
/>
</li>
</ul>
</span>
</template>
<script>
import NavLink from '@/components/Navigation/NavLink';
export default {
name: "DropdownMenu",
props: {
item: {
type: Object,
required: true,
},
},
data() {
return {
isDropdownMenuVisible: false,
isTouchscreenDevice: false
};
},
mounted() {
this.detectTouchscreenDevice();
},
methods: {
openDropdownMenu() {
if (this.isTouchscreenDevice) {
this.isDropdownMenuVisible = !this.isDropdownMenuVisible;
} else {
this.isDropdownMenuVisible = true;
}
},
closeDropdownMenu() {
if (this.isTouchscreenDevice) {
this.isDropdownMenuVisible = false;
} else {
this.isDropdownMenuVisible = false;
}
},
detectTouchscreenDevice() {
if (window.PointerEvent && ('maxTouchPoints' in navigator)) {
if (navigator.maxTouchPoints > 0) {
this.isTouchscreenDevice = true;
}
} else {
if (window.matchMedia && window.matchMedia("(any-pointer:coarse)").matches) {
this.isTouchscreenDevice = true;
} else if (window.TouchEvent || ('ontouchstart' in window)) {
this.isTouchscreenDevice = true;
}
}
return this.isTouchscreenDevice;
}
},
components: {
NavLink
}
};
</script>
<style scoped lang="scss">
.primary-navigation-list-dropdown {
&__toggle {
color: $white;
&:hover {
color: $blue;
}
}
&__menu {
margin-top: 0;
}
&__dropdown {
}
}
.dropdown-menu-list {
&__item {
}
&__link {
&.active,
&.nuxt-link-exact-active {
border-bottom: 1px solid $blue;
}
}
}
</style>
NavLink.vue
<template>
<component
:is="attributes"
v-bind="linkAttributes(attributes.path)"
:title="attributes.title"
:class="[ attributes.cssClasses ]"
class="nav-link active_"
aria-current="page"
prefetch
>
{{ attributes.label }}
</component>
</template>
<script>
export default {
name: 'NavLink',
props: {
attributes: {
type: Object,
required: true
}
},
methods: {
linkAttributes(path) {
if (path.match(/^(http(s)?|ftp):\/\//) || path.target === '_blank') {
return {
is: 'a',
href: path,
target: '_blank',
rel: 'noopener'
}
}
return {
is: 'nuxt-link',
to: path
}
}
}
}
</script>
点击-outside.js
import Vue from 'vue'
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
if (!(el == event.target || el.contains(event.target))) {
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
});
我没有使用 Vue,而是使用 Meteor(其中类似的路由调用导致 Bulma 下拉删除)。
修复它的方法有点笨拙,但是下拉菜单是可见的,因为 .has-dropdown 元素具有 .is-hoverable class。因此,为了解决这个问题,在任何点击下拉菜单项时,我 运行 这个:
// Remove the hover effect = dropdown disappears
$(".has-dropdown").removeClass("is-hoverable");
// Tiny time later, put it back, so hover-drop works anew
setTimeout(function() {
$(".has-dropdown").addClass("is-hoverable");
}, 100);
如果你有一个以上的下拉菜单,它会点击所有的下拉菜单并不重要,因为在路由到新页面时将它们全部重置是无害的。但如果你很挑剔,你可以只定位最近的下拉菜单。
由于 Bulma 的移动视图无论如何都不会激活悬停效果,因此这不会破坏我的导航栏的移动“汉堡菜单”版本。
我的项目在 Chrome、Safari、Firefox 上工作正常。
HERE is a SANDBOX with the issue on it
- 我在导航栏中有一个可悬停的下拉菜单
- 当我转到另一个页面时,下拉菜单仍然打开
- 我已经在普通的 Bulma 中尝试过了,问题仍然存在
- 我正在 Nuxt.js 使用 SSR
- 我正在使用 nuxt-link / 相当于 Vue router-link 来导航到不同的页面。
这是我的 default.vue 文件
<template>
<div class="ch-container">
<header class="ch-header">
<nav
class="navbar is-fixed-top"
role="navigation"
aria-label="main navigation"
>
<div class="navbar-brand">
<nuxt-link class="navbar-item" to="/">
<img
alt="CH Logo"
src="https://i.imgur.com/v35Kfc9.png"
width="28"
height="28"
/>
</nuxt-link>
<a
role="button"
class="navbar-burger burger"
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<nuxt-link class="navbar-item" to="/news">
News
</nuxt-link>
<nuxt-link class="navbar-item" to="/resources">
Resources
</nuxt-link>
<nuxt-link class="navbar-item" to="/tickers">
Tickers
</nuxt-link>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link">
More
</a>
<div class="navbar-dropdown">
<a class="navbar-item">
FAQ
</a>
<nuxt-link class="navbar-item" to="/contact">
Contact
</nuxt-link>
<hr class="navbar-divider" />
<a class="navbar-item">
Feature Request
</a>
</div>
</div>
</div>
<div class="navbar-end">
<div class="navbar-item">
<a href="#">
<fa :icon="faMoon" />
</a>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link is-arrowless">
<fa :icon="faExclamationCircle" />
</a>
<div class="navbar-dropdown">
<a class="navbar-item">
No new notifications
</a>
</div>
</div>
<div class="navbar-item">
<div class="buttons">
<nuxt-link class="button is-primary" to="/signup">
<strong>Sign up</strong>
</nuxt-link>
<nuxt-link id="login" class="button is-light" to="/login">
Log in
</nuxt-link>
</div>
</div>
</div>
</div>
</nav>
</header>
<main class="ch-main">
<nuxt />
</main>
<footer class="ch-footer is-hidden-mobile">
<div class="level">
<div class="level-left">
<div class="level-item">
<a href="#">
<span class="icon">
<fa :icon="faFacebookSquare" />
</span>
</a>
<a href="#">
<span class="icon">
<fa :icon="faTwitterSquare" />
</span>
</a>
<a href="#">
<span class="icon">
<fa :icon="faRedditSquare" />
</span>
</a>
</div>
</div>
<div class="level-right">
<div class="level-item">
©ch, All Rights Reserved
</div>
<div class="level-item">
<nuxt-link to="/contact">Contact</nuxt-link>
</div>
<div class="level-item">
<nuxt-link to="/terms-of-service">Terms</nuxt-link>
</div>
<div class="level-item">
<nuxt-link to="/privacy-policy">Privacy</nuxt-link>
</div>
</div>
</div>
</footer>
</div>
</template>
<script>
import {
faFacebookSquare,
faTwitterSquare,
faRedditSquare,
} from '@fortawesome/free-brands-svg-icons'
import { faMoon, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'
export default {
computed: {
faFacebookSquare() {
return faFacebookSquare
},
faTwitterSquare() {
return faTwitterSquare
},
faRedditSquare() {
return faRedditSquare
},
faMoon() {
return faMoon
},
faExclamationCircle() {
return faExclamationCircle
},
},
mounted() {
// Get all "navbar-burger" elements
const $navbarBurgers = Array.prototype.slice.call(
document.querySelectorAll('.navbar-burger'),
0
)
// Check if there are any navbar burgers
if ($navbarBurgers.length > 0) {
// Add a click event on each of them
$navbarBurgers.forEach((el) => {
el.addEventListener('click', () => {
// Get the target from the "data-target" attribute
const target = el.dataset.target
const $target = document.getElementById(target)
// Toggle the "is-active" class on both the "navbar-burger" and the "navbar-menu"
el.classList.toggle('is-active')
$target.classList.toggle('is-active')
})
})
}
},
}
</script>
<style></style>
这是说明问题的 GIF
移动到其他页面后如何关闭下拉菜单?
好吧,整个事情变得复杂了,因为悬停是由 css 触发的,因此当鼠标悬停在下拉菜单上时总是可以看到它。你必须覆盖这个状态并用 vue 事件解决它。我们还必须在路线上放置一个观察者来重置状态。
<template>
<div class="container">
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<!-- <a class="navbar-item" href="https://bulma.io"> -->
<img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">
<a
role="button"
class="navbar-burger burger"
aria-label="menu"
aria-expanded="false"
data-target="navbarBasicExample"
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item">Home</a>
<a class="navbar-item">Documentation</a>
<div
@mouseover="toggleDropdown(true)"
@mouseleave="toggleDropdown(false)"
class="navbar-item has-dropdown is-hoverable"
>
<a class="navbar-link">More</a>
<div class="navbar-dropdown" :style="{display: showDropdown ? 'block' : 'none' }">
<nuxt-link class="navbar-item" to="/about">About</nuxt-link>
<nuxt-link class="navbar-item" to="/jobs">Jobs</nuxt-link>
</div>
</div>
</div>
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<a class="button is-primary">
<strong>Sign up</strong>
</a>
<a class="button is-light">Log in</a>
</div>
</div>
</div>
</div>
</nav>
<Nuxt/>
</div>
</template>
<script>
export default {
data() {
return {
routeChange: false,
showDropdown: false
};
},
watch: {
$route() {
this.routeChange = true;
this.showDropdown = false;
}
},
methods: {
toggleDropdown(payload) {
if (this.showDropdown !== payload) this.routeChange = false;
if (!this.routeChange) {
this.showDropdown = payload;
}
}
}
};
</script>
这对我有用Bootstrap 5,在触摸设备上它使用点击,在有鼠标的设备上它使用悬停
<template>
<span
v-if="item"
class="primary-navigation-list-dropdown"
@mouseover="isTouchscreenDevice ? null : openDropdownMenu()"
@mouseleave="isTouchscreenDevice ? null : closeDropdownMenu()"
>
<nuxt-link
to="#"
@click.prevent.native="openDropdownMenu"
v-click-outside="closeDropdownMenu"
:title="item.title"
:class="[
item.cssClasses,
{ show: isDropdownMenuVisible }
]"
:id="`navbarDropdownMenuLink-${item.id}`"
:aria-expanded="[isDropdownMenuVisible ? true : false]"
class="
primary-navigation-list-dropdown__toggle
nav-link
dropdown-toggle"
aria-current="page"
role="button"
data-toggle="dropdown"
>
{{ item.label }}
</nuxt-link>
<ul
:class="{ show: isDropdownMenuVisible }"
:aria-labelledby="`navbarDropdownMenuLink-${item.id}`"
class="
primary-navigation-list-dropdown__menu
dropdown-menu-list
dropdown-menu"
>
<li
v-for="item in item.children" :key="item.id"
class="dropdown-menu-list__item"
>
<NavLink
:attributes="item"
class="dropdown-menu-list__link dropdown-item"
/>
</li>
</ul>
</span>
</template>
<script>
import NavLink from '@/components/Navigation/NavLink';
export default {
name: "DropdownMenu",
props: {
item: {
type: Object,
required: true,
},
},
data() {
return {
isDropdownMenuVisible: false,
isTouchscreenDevice: false
};
},
mounted() {
this.detectTouchscreenDevice();
},
methods: {
openDropdownMenu() {
if (this.isTouchscreenDevice) {
this.isDropdownMenuVisible = !this.isDropdownMenuVisible;
} else {
this.isDropdownMenuVisible = true;
}
},
closeDropdownMenu() {
if (this.isTouchscreenDevice) {
this.isDropdownMenuVisible = false;
} else {
this.isDropdownMenuVisible = false;
}
},
detectTouchscreenDevice() {
if (window.PointerEvent && ('maxTouchPoints' in navigator)) {
if (navigator.maxTouchPoints > 0) {
this.isTouchscreenDevice = true;
}
} else {
if (window.matchMedia && window.matchMedia("(any-pointer:coarse)").matches) {
this.isTouchscreenDevice = true;
} else if (window.TouchEvent || ('ontouchstart' in window)) {
this.isTouchscreenDevice = true;
}
}
return this.isTouchscreenDevice;
}
},
components: {
NavLink
}
};
</script>
<style scoped lang="scss">
.primary-navigation-list-dropdown {
&__toggle {
color: $white;
&:hover {
color: $blue;
}
}
&__menu {
margin-top: 0;
}
&__dropdown {
}
}
.dropdown-menu-list {
&__item {
}
&__link {
&.active,
&.nuxt-link-exact-active {
border-bottom: 1px solid $blue;
}
}
}
</style>
NavLink.vue
<template>
<component
:is="attributes"
v-bind="linkAttributes(attributes.path)"
:title="attributes.title"
:class="[ attributes.cssClasses ]"
class="nav-link active_"
aria-current="page"
prefetch
>
{{ attributes.label }}
</component>
</template>
<script>
export default {
name: 'NavLink',
props: {
attributes: {
type: Object,
required: true
}
},
methods: {
linkAttributes(path) {
if (path.match(/^(http(s)?|ftp):\/\//) || path.target === '_blank') {
return {
is: 'a',
href: path,
target: '_blank',
rel: 'noopener'
}
}
return {
is: 'nuxt-link',
to: path
}
}
}
}
</script>
点击-outside.js
import Vue from 'vue'
Vue.directive('click-outside', {
bind: function (el, binding, vnode) {
el.clickOutsideEvent = function (event) {
if (!(el == event.target || el.contains(event.target))) {
vnode.context[binding.expression](event);
}
};
document.body.addEventListener('click', el.clickOutsideEvent)
},
unbind: function (el) {
document.body.removeEventListener('click', el.clickOutsideEvent)
},
});
我没有使用 Vue,而是使用 Meteor(其中类似的路由调用导致 Bulma 下拉删除)。
修复它的方法有点笨拙,但是下拉菜单是可见的,因为 .has-dropdown 元素具有 .is-hoverable class。因此,为了解决这个问题,在任何点击下拉菜单项时,我 运行 这个:
// Remove the hover effect = dropdown disappears
$(".has-dropdown").removeClass("is-hoverable");
// Tiny time later, put it back, so hover-drop works anew
setTimeout(function() {
$(".has-dropdown").addClass("is-hoverable");
}, 100);
如果你有一个以上的下拉菜单,它会点击所有的下拉菜单并不重要,因为在路由到新页面时将它们全部重置是无害的。但如果你很挑剔,你可以只定位最近的下拉菜单。
由于 Bulma 的移动视图无论如何都不会激活悬停效果,因此这不会破坏我的导航栏的移动“汉堡菜单”版本。
我的项目在 Chrome、Safari、Firefox 上工作正常。