自定义 Vue 下拉列表保持焦点



我试图在这里找到解决方案,但不能,决定写一个问题。

所以我正在尝试在 Vue 中构建一个简单的下拉菜单组件,该组件具有切换按钮,用户可以自由单击并在项目周围按 Tab 键,但一旦焦点离开组件,菜单就会折叠。

现在,我保持了对@blur@focus事件的专注,但我的Toggle按钮有问题。如果我将相同的侦听器附加到它,则单击它会显示并立即隐藏菜单,因此您必须再次单击才能将其展开。

这是演示问题的小提琴。

但是,如果我删除了侦听器,那么在焦点在组件内部后单击按钮是有问题的。 我想我的方法是错误的,所以这是预期的行为:

  • Toggle按钮就是这样 - 一个切换按钮。它应该在点击时折叠/展开列表
  • 列表只能通过在关闭时单击Toggle按钮来展开
  • 列表在展开时单击Toggle按钮或聚焦于列表,从而折叠起来。这包括将焦点放在按钮上(即单击切换按钮以展开列表,然后单击外部的某个位置而不聚焦任何项目(。

编辑:多亏了@Sphinx我设法让下拉列表按预期工作。这是更新的小提琴。

对于您的情况,正如问题下的评论已经指出的那样,您必须处理许多情况,例如@focus@click将连续触发,单击其中任何一个时将触发@blur<button>@blur<ul></li>

这不是一个好主意。但是你可以检查这个小提琴,它是setTimeoutclearTimeout的一个解决方案。然后你可能已经看到它延迟了 100 毫秒setTimeout(()=>{}, 100)(我添加了一些日志,你可以打开浏览器控制台检查工作流程(。原因是我们必须等待足够的时间来确保下一个事件处理程序(例如首先触发焦点,然后单击将在稍后触发(可以及时清除上一个setTimeout,除非菜单可能先打开,然后再次关闭。(PS:对于一些老机器来说,100ms可能还不够,这取决于当前渲染完成的速度(

一个解决方案

  1. 删除@focus@blur

  2. this.showMenu为 true(打开(时,添加一个侦听器=click对于 Dom=文档,它将在触发时this.hide()执行。

  3. 然后在this.hide()中,从 Dom=document 中删除该侦听器 =click

  4. 为了防止在单击按钮和菜单时菜单被折叠,添加修饰符=停止,它将停止单击事件传播到上层 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;
}

最新更新