最近做了一个 vue2 + vant 的项目。遇到了一个很奇怪的问题,输入框有时候无法唤醒键盘。
通过 deepseek 的帮助,得到了一些解决方案。
测试的代码如下,这两个框都可以正常唤醒键盘
<input
value="input1"
@pointerdown="handlePointerDown"
/>
<input
value="input2"
ref="myInput"
@click="forceFocus"
@touchstart="forceFocus"
/>
{
methods: {
forceFocus() {
this.$refs.myInput.focus();
// 额外延迟触发,确保 Windows 接收事件
setTimeout(() => {
this.$refs.myInput.focus();
}, 100);
},
handlePointerDown(e) {
e.preventDefault();
e.target.focus();
},
}
}
原因分析:
可能还是因为 input 框没有实际的 focus ,或者是 focus 后系统卡顿,导致又失去焦点了。导致系统没有识别到需要输入的操作,所以才有了e.preventDefault() 与延时再次进行 focus
根据上面的代码,将这部分代码写成一个指令
仅适合输入框少的情况,这事件监听过多,存在效率问题
export default {
inserted(el) {
const input = el.querySelector('input');
if (input) {
el.addEventListener('pointerdown', (e) => {
e.preventDefault();
input.focus();
setTimeout(() => input.focus(), 50);
});
}
}
}
再 main.js 中引入这个指令
import Vue from 'vue'
import focusTouch from '@/utils/directives/focusTouch'
Vue.directive('focus-touch', focusTouch)
在 vue 中使用这个指令
<van-field
v-model="value"
v-focus-touch
clearable
clickable
name="name"
label="姓名"
placeholder="请输入姓名"
/>
======= 更新 =======
上面代码经过测试会出现一个问题,当页面 input 框过多,可能会导致卡顿,或者失灵,现在增加了一个更简洁的方案
使用新的指令,该指令只需要在根元素上绑定一次,全局生效。不需要给每个元素再次绑定了
// directives/focusTouch.js
export default {
inserted(el) {
const handlePointerDown = (e) => {
// 只处理 input/textarea 且未被禁用
const target = e.target.closest('input:not(:disabled), textarea:not(:disabled)');
if (target) {
if (document.activeElement !== target) {
e.preventDefault();
target.focus({ preventScroll: true }); // 防止意外滚动
}
}
};
el.addEventListener('pointerdown', handlePointerDown, { passive: false });
el._focusTouchHandler = handlePointerDown;
},
unbind(el) {
if (el._focusTouchHandler) {
el.removeEventListener('pointerdown', el._focusTouchHandler);
}
}
}
vue 绑定方式
<!-- App.vue -->
<template>
<div id="app" v-focus-touch> <!-- 指令绑定在这里 -->
<router-view/> <!-- 所有页面内容都会继承这个监听 -->
</div>
</template>
✅ 哪些元素会被捕获?
所有原生 <input> 元素
<input type="text"> <!-- 会触发 -->
组件库的输入框(如 Vant/ElementUI)
<van-field> <!-- 内部生成的 input 会触发 -->
<el-input> <!-- 内部生成的 input 会触发 -->
<textarea> 和其他可聚焦元素
(如果选择器包含它们时也会触发)
❌ 哪些元素不会触发?
被排除的元素
// 指令中已通过 :not(:disabled) 排除禁用状态元素
<input disabled> <!-- 不会触发 -->
被阻止冒泡的元素
<div @pointerdown.stop>
<input> <!-- 不会触发(事件被阻止冒泡到顶级容器) -->
</div>
CSS 阻止指针事件的元素
input {
pointer-events: none; /* 不会触发 */
}
使用说明
生效范围:所有未被排除的可聚焦输入元素
冒泡机制:依赖事件冒泡到顶级容器
特殊场景:注意第三方组件库的DOM结构差异
提示:可通过添加特定class(如.focus-touch-input)实现更精确的控制