我试图在这里找到解决方案,但不能,决定写一个问题。
所以我正在尝试在 Vue 中构建一个简单的下拉菜单组件,该组件具有切换按钮,用户可以自由单击并在项目周围按 Tab 键,但一旦焦点离开组件,菜单就会折叠。
现在,我保持了对@blur
和@focus
事件的专注,但我的Toggle
按钮有问题。如果我将相同的侦听器附加到它,则单击它会显示并立即隐藏菜单,因此您必须再次单击才能将其展开。
这是演示问题的小提琴。
但是,如果我删除了侦听器,那么在焦点在组件内部后单击按钮是有问题的。 我想我的方法是错误的,所以这是预期的行为:
Toggle
按钮就是这样 - 一个切换按钮。它应该在点击时折叠/展开列表- 列表只能通过在关闭时单击
Toggle
按钮来展开 - 列表在展开时单击
Toggle
按钮或聚焦于列表,从而折叠起来。这包括将焦点放在按钮上(即单击切换按钮以展开列表,然后单击外部的某个位置而不聚焦任何项目(。
编辑:多亏了@Sphinx我设法让下拉列表按预期工作。这是更新的小提琴。
对于您的情况,正如问题下的评论已经指出的那样,您必须处理许多情况,例如@focus
和@click
将连续触发,单击其中任何一个时将触发@blur
<button>
和@blur
<ul></li>
。
这不是一个好主意。但是你可以检查这个小提琴,它是setTimeout
和clearTimeout
的一个解决方案。然后你可能已经看到它延迟了 100 毫秒setTimeout(()=>{}, 100)
(我添加了一些日志,你可以打开浏览器控制台检查工作流程(。原因是我们必须等待足够的时间来确保下一个事件处理程序(例如首先触发焦点,然后单击将在稍后触发(可以及时清除上一个setTimeout
,除非菜单可能先打开,然后再次关闭。(PS:对于一些老机器来说,100ms可能还不够,这取决于当前渲染完成的速度(
一个解决方案:
-
删除
@focus
和@blur
-
当
this.showMenu
为 true(打开(时,添加一个侦听器=click
对于 Dom=文档,它将在触发时this.hide()
执行。 -
然后在
this.hide()
中,从 Dom=document 中删除该侦听器 =click
。 -
为了防止在单击按钮和菜单时菜单被折叠,添加修饰符=停止,它将停止单击事件传播到上层 Dom 节点。
如果将<button>
和<ul>
包装成一个<div>
,那么只需要添加修饰符=stop
像<template><div @click.stop><button></button<ul>...<ul></div></template>
。
下面是一个演示:
Vue.config.productionTip = false
new Vue({
el: "#app",
data: {
showMenu: false,
items: ['Option 1', 'Option 2', 'Option 3', 'Option 4']
},
computed: {
listClass: function() {
if (this.showMenu) {
return 'show';
}
return '';
}
},
methods: {
toggle: function() {
this.showMenu = !this.showMenu
this.showMenu && this.$nextTick(() => {
document.addEventListener('click', this.hide)
})
},
hide: function() {
this.showMenu = false
document.removeEventListener('click', this.hide)
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
ul {
list-style: none;
display: none;
}
li {
padding: 5px;
border: 1px solid #000;
}
li:hover {
cursor: pointer;
background: #aaa;
}
.show {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<h3>
This is one demo:
</h3>
<button @click.stop="toggle" tabindex="0">
Toogle menu
</button>
<ul :class="listClass" @click.stop>
<li v-for="(item, key) in items" tabindex="0">{{ item }}</li>
</ul>
</div>
我不确定这是否解决了您要完成的任务,但您可以尝试使用 @mouseenter和@mouseout
<button
@click="toggle"
tabindex="0"
@mouseenter="itemFocus"
@mouseout="itemBlur"
>
Toogle menu
这是小提琴:https://jsfiddle.net/xhmsf9yw/5/
我大约 40% 确定我理解了这个问题......
根据您的描述,该按钮似乎不应该是切换开关,而应该是打开的。
模板:
<div id="app">
<button
@click="showMenu"
tabindex="0"
@focus="itemFocus"
@blur="itemBlur"
>
Toogle menu
</button>
<ul :class="listClass">
<li
v-for="(item, key) in items"
tabindex="0"
@focus="itemFocus"
@blur="itemBlur"
>{{ item }}</li>
</ul>
</div>
方法:
showMenu: function(){
this.showMenu = true;
},
这将在菜单关闭时打开菜单,但不会关闭它
https://jsfiddle.net/wc1oehx9/
这些类型的 UI 具有多种交互方式的诀窍是设置 UI 元素的状态,并让 UI 元素反映该状态。
所以我所做的是这样的:
- 如果鼠标进入,菜单应打开
- 如果鼠标离开,菜单应关闭
- 如果聚焦,则菜单应打开
- 如果它模糊,菜单应该很接近
所以我的解决方案是:https://jsfiddle.net/jaiko86/e0vtf2k1/1/
.HTML:
<div id="app">
<button
tabindex="0"
@focus="isMenuOpen = true"
@blur="isMenuOpen = false"
@mouseenter="isMenuOpen = true"
@mouseout="isMenuOpen = false"
>
Toogle menu
</button>
<ul :class="listClass">
<li
v-for="(item, key) in items"
tabindex="0"
@focus="isMenuOpen = true"
@blur="isMenuOpen = false"
@mouseenter="isMenuOpen = true"
@mouseout="isMenuOpen = false"
>{{ item }}</li>
</ul>
</div>
.JS:
// simplified for clarity
new Vue({
el: "#app",
data: {
showMenu: false,
items: [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ],
isMenuOpen: false,
},
computed: {
listClass() { // you can omit the `: function` in new ES standard
return this.isMenuOpen ? 'show' : ''; //ternary op saves lines
},
methods: {
// not needed, as it's done in HTML
// toggle: function(){
// this.showMenu = !this.showMenu
//},
/*
we no longer need these methods:
itemFocus: function() {
var self = this;
Vue.nextTick(function() {
if(!self.showMenu) {
self.showMenu = true;
}
});
},
itemBlur: function() {
var self = this;
Vue.nextTick(function() {
if(self.showMenu) {
self.showMenu = false;
}
});
}
*/
}
})
我遇到了类似的问题,我参考了下面的代码来解决它。有关其工作原理的深入解释,请参阅我引用的网站链接。
这是 HTML:
<nav class="flex items-center justify-between h-full p-3 m-auto bg-orange-200">
<span>My Logo</span>
<div class="relative">
<button id="user-menu" aria-label="User menu" aria-haspopup="true">
<img
class="w-8 h-8 rounded-full"
src="https://scontent.fcpt4-1.fna.fbcdn.net/v/t1.0-1/p480x480/82455849_2533242576932502_5629407411459588096_o.jpg?_nc_cat=100&ccb=2&_nc_sid=7206a8&_nc_ohc=rGM_UBdnnA8AX_pGIdM&_nc_ht=scontent.fcpt4-1.fna&tp=6&oh=7de8686cebfc29e104c118fc3f78c7e5&oe=5FD1C3FE"
/>
</button>
<div
id="user-menu-dropdown"
class="absolute right-0 w-48 mt-2 origin-top-right rounded-lg shadow-lg top-10 menu-hidden"
>
<div
class="p-4 bg-white rounded-md shadow-xs"
role="menu"
aria-orientation="vertical"
aria-labelledby="user-menu"
>
<a
href="#"
class="block px-6 py-2 mb-2 font-bold rounded"
role="menuitem"
>My profile</a
>
<a href="#" class="block px-6 py-2 font-bold rounded" role="menuitem"
>Logout</a
>
</div>
</div>
</div>
</nav>
这是 CSS:
#user-menu ~ #user-menu-dropdown {
transform: scaleX(0) scaleY(0);
transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
transition-duration: 75ms;
opacity: 0;
top: 3.25rem;
}
#user-menu ~ #user-menu-dropdown:focus-within,
#user-menu:focus ~ #user-menu-dropdown {
transform: scaleX(1) scaleY(1);
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
transition-duration: 100ms;
opacity: 1;
}