在Vue中实现回车键切换焦点

简介: 在Vue中实现回车键切换焦点

几乎在所有浏览器中,都具有 Tab 键切换焦点的功能。

但是任性的用户强烈要求一定要有 Enter 键切换焦点的功能。

为了交付上线拿到钱,我们只好再一次毫无原则性的接受了客户的需求。

在上一代人中,大多都有这种操作习惯。习惯把保存称为编辑,习惯用回车替换 Tab。这是受到微软 excel 荼毒的结果。

起初我以为这个功能很简单,无非就是把 Enter 键的功能转接到 Tab 键上面,分分钟就可以解决掉的问题。

可困难马上就出现了,我发现这条路是走不通的。

我们经常可以主动触发某个事件,比如el.click()就可以调用点击事件,或者使用dispatchEvent。但是键盘和鼠标事件却不行。

我查阅了很多资料,也做了很多尝试。最后总结出来一个结论,在浏览器中,JavaScript 无法操作用户的键盘或者鼠标,这是出于安全策略的考虑。仔细想一下,如果可以用一段 JavaScript 脚本控制用户键盘和鼠标的话,那么用户只需要打开一个黑客网站,黑客就可以瞬间得到他想得到的一切。

所以,如果要通过除 Tab 键以外的其他方式来触发焦点切换,focus几乎是唯一的选择。


在原生页面中实现回车键切换焦点


项目是基于 vue 和 element-ui 做的,为了把实现思路先讲清楚,暂时把这些抛开,从原生的页面中寻找答案。

以下是一个原生的 html 页面。


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Demo</title>
  </head>
  <body>
    <form>
      <input placeholder="姓名" />
      <input placeholder="性别" />
      <input placeholder="年龄" />
    </form>
  </body>
</html>

接下来要实现通过回车键切换焦点,我把思路梳理如下:

  1. 监听回车键按下事件。
  2. 获取当前聚焦元素。
  3. 获取下一个要被聚焦的元素。
  4. 切换焦点。

思路有了,实现起来也非常简单。


1.监听回车键按下事件


在文档中添加 script 标签,写入如下代码。


function enterCallback(e) {
  if (e.keyCode === 13) {
    // 按下回车后的逻辑
  }
}
window.addEventListener("keydown", enterCallback);

要注意,enterCallback单独拿出来,用于注销监听事件。

监听按键事件最常用的方法就是使用事件委托,将事件绑定到window对象上。相比较给每一个元素都绑定一个事件的方式,这样做的最大好处就是节省内存空间,性能更好。

判断按下哪个键的方式有很多,比如判断e.keye.code或者e.keyCode等方式。但绝大多数的情况下都建议使用e.keyCode。下面是一张来自网络的keyCode表。


2.获取当前聚焦元素


很容易就可以做到这一步。

常见的有两种方式。第一种是e.target,第二种是document.activeElement。这种情况下,个人更推荐使用第二种。


function enterCallback(e) {
  if (e.keyCode === 13) {
    let activeEl = document.activeElement;
  }
}


3.获取下一个要被聚焦的元素


这一步也比较容易。使用el.nextElementSiblingAPI 即可获取。


function enterCallback(e) {
  if (e.keyCode === 13) {
    let activeEl = document.activeElement;
    let nextEl = activeEl.nextElementSibling;
  }
}


4.切换焦点


切换焦点调用focus即可实现。


function enterCallback(e) {
  if (e.keyCode === 13) {
    let activeEl = document.activeElement;
    let nextEl = activeEl.nextElementSibling;
    nextEl && nextEl.focus();
  }
}

至此一个最简单的 Demo 已经实现了,接下来看看项目中实际的情况。


在 element-ui 项目中实现回车键切换焦点


因为是使用组件开发,加上样式等因素,dom 节点并不像上面写的原生 Demo 那么简单,实际情况是多层嵌套的。下面是实际生成的代码结构。


<div
  class="el-form-item el-form-item--small"
  style="margin-bottom: 0vh; width: 25%; display: inline-block;"
>
  <label for="pactcode" class="el-form-item__label" style="width: 130px;"
    >协议号</label
  >
  <div class="el-form-item__content" style="margin-left: 130px;">
    <div class="el-input el-input--small">
      <!---->
      <input
        type="text"
        autocomplete="off"
        id="el-input"
        placeholder="未填写协议号"
        class="el-input__inner"
      />
      <!---->
    </div>
  </div>
</div>

可以看到,如果每一个输入框都是这种类型的嵌套结构,上面的方法是无法直接解决的。因为nextElementSiblingAPI 只能找到下一个兄弟元素,而在这里 input 明显找不到下一个兄弟元素。

思路是,通过回溯的手段朝外层寻找,直到找到一个类名包含el-form-itemel-form-item--small的祖级元素,然后再从这个祖级元素的下一个兄弟元素中寻找类名包含el-input__inner的 input 元素。

所以要再写两个函数,分别是寻找祖级元素的findFormItem和寻找 input 元素的findInput

findFormItem:


function findFormItem(el) {
  const parent = el.parentElement;
  if (!parent) return document.body;
  if (
    parent.className.includes("el-form-item") &&
    parent.className.includes("el-form-item--small")
  ) {
    return parent;
  }
  return findFormItem(parent);
}

findInput:


function findInput(container) {
  let nextEl = container.nextElementSibling;
  if (!nextEl) return;
  let input = nextEl.querySelector("input");
  while (input.id === "el-select") {
    nextEl = nextEl.nextElementSibling;
    if (!nextEl) return;
    input = nextEl.querySelector("input");
  }
  if (input.className.includes("el-input__inner")) return input;
}

有了这两个函数以后,实现回车切换焦点就非常简单了。只需要执行两行代码。


const container = findFormItem(document.activeElement);
findInput(container) && findInput(container).focus();

完整的代码大概是这样的。

methods中声明三个方法。


methods: {
    addEnterListener() {
      if (window.__completeEnterBind__) return;
      window.addEventListener("keydown", this.enterCallback);
      window.__completeEnterBind__ = true;
    },
    removeEnterListener() {
      window.removeEventListener("keydown", this.enterCallback);
      window.__completeEnterBind__ = false;
    },
    enterCallback(e) {
      function findFormItem(el) {
        const parent = el.parentElement;
        if (!parent) return document.body;
        if (
          parent.className.includes("el-form-item") &&
          parent.className.includes("el-form-item--small")
        ) {
          return parent;
        }
        return findFormItem(parent);
      }
      function findInput(container) {
        let nextEl = container.nextElementSibling;
        if (!nextEl) return;
        let input = nextEl.querySelector("input");
        while (input.id === "el-select") {
          nextEl = nextEl.nextElementSibling;
          if (!nextEl) return;
          input = nextEl.querySelector("input");
        }
        if (input.className.includes("el-input__inner")) return input;
      }
      if (e.keyCode === 13) {
        const container = findFormItem(document.activeElement);
        findInput(container) && findInput(container).focus();
      }
    }
}

然后在mounted中添加回车监听和在destroy中移除回车键听。


mounted() {
  this.addEnterListener();
},
destroy() {
  this.removeEnterListener();
},

需要注意的是,项目是多标签页的形式,表单组件可能会被渲染多次,所以通过在 window 对象上添加一个__completeEnterBind__字段来确保回车换行事件正确绑定。

这样,又完成了一次用户的特殊需求,距离拿到项目款项又近了一步。


相关文章
|
3天前
|
JavaScript
Vue中如何设置在执行删除等危险操作时给用户提示(二次确认后执行对应的操作)
这篇文章介绍了在Vue项目中如何实现执行删除等危险操作时的二次确认机制,使用Element UI的`el-popconfirm`组件来弹出确认框,确保用户在二次确认后才会执行删除操作。
Vue中如何设置在执行删除等危险操作时给用户提示(二次确认后执行对应的操作)
|
3天前
|
JavaScript
如何创建一个Vue项目(手把手教你)
这篇文章是一篇手把手教读者如何创建Vue项目的教程,包括使用管理员身份打开命令行窗口、找到存放项目的位置、通过vue-cli初始化项目、填写项目信息、进入项目目录、启动项目等步骤,并提供了一些常见第三方库的引入方法。
如何创建一个Vue项目(手把手教你)
|
3天前
|
前端开发
StringBoot+Vue实现游客或用户未登录系统前、可以浏览商品等信息、但是不能购买商品或者加入购物车等操作。登录系统显示用户的登录名(源码+讲解)
这篇文章介绍了使用StringBoot+Vue实现用户登录状态判断的方法,包括前端加载用户信息和后端设置session的源码示例。
|
4天前
|
JavaScript 编译器
成功解决:Module build failed: Error: Vue packages version mismatch
这篇文章记录了解决Vue项目中遇到的"Module build failed: Error: Vue packages version mismatch"错误的步骤,原因是项目中Vue依赖的版本不一致,解决方法是删除`node_modules`后重新安装指定版本的Vue和`vue-template-compiler`,确保版本匹配,最终成功运行项目。
成功解决:Module build failed: Error: Vue packages version mismatch
|
4天前
|
JavaScript
在Vue中使用Avue、配置过程以及实际应用
这篇文章介绍了作者在Vue项目中集成Avue组件库的完整过程,包括安装、配置和实际应用,展示了如何利用Avue实现动态表单和数据展示的功能。
在Vue中使用Avue、配置过程以及实际应用
|
4天前
|
JavaScript
Vue devDependencies 与 dependencies 能别
Vue devDependencies 与 dependencies 能别
10 3
|
3天前
|
JavaScript
如何在Vue页面中引入img下的图片作为背景图。../的使用
这篇文章介绍了在Vue页面中如何引入`img`目录下的图片作为背景图,提供了两种使用相对路径的方法。第一种是使用`../assets/img/`作为路径引入图片,第二种是使用`../../assets/img/`作为路径。文章还展示了使用这些方法的代码实现和效果展示,并鼓励读者学无止境。
如何在Vue页面中引入img下的图片作为背景图。../的使用
|
3天前
|
JavaScript
如何通过点击商品的信息(图片或者文字)跳转到更加详细的商品信息介绍(前后端分离之Vue实现)
该博客文章介绍了如何在Vue 2框架下实现前后端分离的商品信息展示和详情页跳转,包括排序筛选、详情展示、加入购物车和分享功能。
如何通过点击商品的信息(图片或者文字)跳转到更加详细的商品信息介绍(前后端分离之Vue实现)
|
3天前
|
JavaScript
在Vue中使用Swiper轮播图、同时解决点击轮播图左右切换按钮不生效的问题、同时将轮播图抽离出为一个公共组件
这篇文章介绍了在Vue中如何使用Swiper插件创建轮播图,解决Swiper左右切换按钮不生效的问题,并展示了如何将Swiper轮播图抽离成一个可复用的公共组件,同时提供了详细的安装、配置和优化建议。
在Vue中使用Swiper轮播图、同时解决点击轮播图左右切换按钮不生效的问题、同时将轮播图抽离出为一个公共组件
|
4天前
|
JavaScript
Vue学习之--------插槽【默认插槽、具名插槽、作用域插槽】
这篇文章详细介绍了Vue.js中的插槽概念,包括默认插槽、具名插槽和作用域插槽的使用方式和实际应用示例,通过代码演示了如何在组件中定义和使用插槽来实现内容的灵活替换和展示。
Vue学习之--------插槽【默认插槽、具名插槽、作用域插槽】