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

时间:2018-09-20 21:51:32

标签: javascript vue.js vuejs2 frontend

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

因此,我试图在Vue中构建一个简单的下拉菜单组件,该组件具有切换按钮,用户可以自由单击并在项目周围进行制表,但是只要焦点离开组件,菜单就会折叠。

现在,我通过@blur@focus事件来保持焦点,但是我对Toggle按钮有疑问。如果我在其上附加了相同的侦听器,则单击它会显示并立即隐藏菜单,因此您必须再次单击以展开它。

这里是fiddle,说明了问题。

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

  • Toggle按钮就是这样-一个切换按钮。它应该折叠/扩展点击列表
  • 仅在关闭时单击Toggle按钮才能展开列表
  • 通过展开时单击Toggle按钮或将其聚焦在列表上,可以折叠该列表。这包括将重点放在按钮上(即,单击切换按钮以展开列表,然后单击外部某处而不聚焦任何项目)。

编辑:感谢@Sphinx,我设法使下拉菜单按预期运行。这是更新后的fiddle

4 个答案:

答案 0 :(得分:0)

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

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

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

答案 1 :(得分:0)

我大概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/

答案 2 :(得分:0)

使用多种交互方式的这些类型的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;
        }
      });
    }
    */
  }
})

答案 3 :(得分:0)

对于您的情况,正如已经在问题下所指出的那样,您必须处理许多情况,例如@focus@click将连续触发,{单击@blur中的{1}}和<button>时将触发它们。

这不是一个好主意。但是您可以使用@blur<ul></li>来检查This fiddle,它是一个解决方案。然后您可能已经看到它通过setTimeout延迟了100毫秒(我添加了一些日志,您可以打开浏览器控制台来检查工作流程)。 原因是我们必须等待足够的时间,以确保下一个事件处理程序(例如先触发焦点,然后再触发单击)可以清除先前的{{1} } 及时,除非菜单可能先打开,然后再关闭。 (PS:对于某些旧机器,100ms可能不够,这取决于完成当前渲染的速度)

一种解决方案

  1. 删除clearTimeoutsetTimeout(()=>{}, 100)

  2. setTimeout为真(打开)时,为Dom = document添加一个listener = {@focus,它将在触发时执行@blur

  3. 然后在this.showMenu中,从Dom = document中删除该listener = click

  4. 为防止在单击按钮和菜单时菜单被折叠,请添加modifier = stop,它将停止单击事件向上级Dom节点的传播。

如果将this.hide()this.hide()包装成一个click,则只需像<button>那样添加修饰符= <ul>

下面是一个演示

<div>
stop
<template><div @click.stop><button></button<ul>...<ul></div></template>