自定义Vue下拉菜单保持焦点

a7qyws3x  于 2022-12-23  发布在  Vue.js
关注(0)|答案(5)|浏览(381)

我试着在这里找到这个问题的解决方案,但不能,并决定写一个问题。
因此,我尝试在Vue中构建一个简单的下拉菜单组件,该组件具有切换按钮,用户可以自由地点击和标记项目,但一旦焦点离开组件,菜单就会折叠。
现在,我用@blur@focus事件维护了焦点,但是Toggle按钮有一个问题,如果我将相同的侦听器附加到它,那么单击它会显示并立即隐藏菜单,因此您必须再次单击以展开它。
下面是fiddle演示的问题。
但是,如果我删除了侦听器,那么在焦点位于组件内部之后单击按钮就会出现问题。我想我的方法是错误的,所以下面是预期的行为:

  • Toggle按钮只是一个 * 切换按钮 *。它应该在单击时折叠/展开列表
  • 当列表关闭时,只能通过单击Toggle按钮来展开列表
  • 当列表展开时,单击Toggle按钮或将焦点移到列表外,可以折叠列表。这包括将焦点移到按钮上(即单击切换按钮展开列表,然后单击列表外的某个位置,但不聚焦任何项目)。
    EDIT:感谢@Sphinx,我成功地让下拉菜单工作,因为我期望它。这里是更新的fiddle
nimxete2

nimxete21#

对于您的案例,正如问题下方的评论所指出的,您必须处理许多情况,例如@focus@click将连续触发,<button>@blur<ul></li>@blur将在单击其中任何一个时触发。
不是一个好主意。但是你可以检查This fiddle,它是setTimeoutclearTimeout一个解决方案。然后你可能已经看到它延迟了setTimeout(()=>{}, 100) 100ms(我加了些木头,您可以打开浏览器控制台检查工作流)。原因是我们必须等待足够的时间以确保下一个事件处理程序(比如先触发焦点,然后再触发点击)可以及时清除之前的setTimeout,除非菜单可能先打开,然后再关闭。(PS:对于一些旧机器,100ms可能不够,这取决于当前渲染完成的速度)

    • 一个解决方案**:

1.删除@focus@blur
1.当this.showMenu为true(已打开)时,为Dom = document添加一个侦听器= click,它将在触发时执行this.hide()
1.然后在this.hide()中,从Dom = document中删除侦听器= click
1.为了防止在点击按钮和菜单时菜单塌陷,添加修饰符= stop,它将停止点击事件向上层Dom节点的传播。
如果将<button><ul> Package 为一个<div>,则只需像<template><div @click.stop><button></button<ul>...<ul></div></template>一样添加修饰符= stop
以下是一个演示

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

vjrehmav2#

我不确定这是否能解决您要完成的任务,但您可以尝试使用@mouseenter和@mouseout

<button 
@click="toggle" 
tabindex="0"
@mouseenter="itemFocus"
@mouseout="itemBlur"
>
Toogle menu

小提琴在这里:https://jsfiddle.net/xhmsf9yw/5/

qnakjoqk

qnakjoqk3#

我有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/

xpszyzbs

xpszyzbs4#

这些具有多种交互方式的UI类型的技巧是设置UI元素的状态,并让UI元素反映该状态。
所以我做的是这样的:

  • 如果鼠标进入,菜单应打开
  • 如果鼠标离开,菜单应该关闭
  • 如果已聚焦,则菜单应打开
  • 如果模糊不清,菜单应该关闭

我的解决方案是:https://jsfiddle.net/jaiko86/e0vtf2k1/1/
超文本:

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

联森:

// 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;
        }
      });
    }
    */
  }
})
jpfvwuh4

jpfvwuh45#

我曾经遇到过类似的问题,我参考了下面的代码来解决它。要深入解释这个问题是如何工作的,请访问我提到的网站link
这是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;
}

相关问题