公司最近在一个项目中,要求一个文本框可以进行编辑。故在文本框后面增加一个按钮,点击按钮可以切换为输入框,并使得输入框获取焦点,变成激活状态。
这个需求挺简单的,项目用的 Vue,实现很容易。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <div id="app"> <span v-if="!editMode">{{ msg }}</span> <input v-if="editMode" v-model="msg" ref="inputRef" /> <button @click="onEditClick">edit</button> </div> </template>
<script> export default { data() { return { editMode: false, msg: "hello world", }; }, methods: { onEditClick() { this.editMode = true; setTimeout(() => { this.$refs.inputRef.focus(); }, 10); }, }, }; </script>
|
点击按钮后,由于涉及 v-if
切换,故需要使用 $nextTick
,等待 DOM 渲染完成后,才能获取 $refs.inputRef
。
但是实际项目中,还有一些其他需求,干脆就直接使用 setTimeout
了。
这么做,PC 端还有 Android 都没有问题。但是 iOS 会无效,表现为可以通过 $refs
获取 DOM 元素,但是 focus()
不生效。
一开始以为是延时太小,导致了其他逻辑干扰,发现不行。
疑似自己封装的组件有问题,反复调试修改,还是不行。又尝试把封装的 Input
组件,换成标准的 input
组件,依然不行。
写一个小的 demo,iOS 确实可以实现 focus()
。这也证明了还是代码问题。而且是项目页面问题。
最后发现,出在了 setTimeout
上。iOS 操作输入框,必须用户触发并同步立即执行对应功能,不能延迟处理。
CodeSandBox 演示地址
最终代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <template> <div id="app"> <span v-if="!editMode">{{ msg }}</span> <input v-if="editMode" v-model="msg" ref="inputRef" /> <button @click="onEditClick">edit</button> </div> </template>
<script> export default { data() { return { editMode: false, msg: "hello world", }; }, methods: { onEditClick() { this.editMode = true; this.$nextTick(() => { this.$refs.inputRef.focus(); setTimeout(() => { }, 10); }); }, }, }; </script>
|
之前在做 input
输入框,拉起文件/相册时候,是知道这个问题的。而且 iOS Android 都要求有用户真实点击,才能模拟用户点击。大致如下:
1 2 3 4 5 6 7 8 9
| <div id="btn">btn</div> <script> document.querySelector('#btn').addEventListener('click', () => { const input = document.createElement('input') input.type = 'file' document.body.appendChild(input) input.click() }) </script>
|
如果没有用户真实的点击,那么直接执行创建元素并模拟点击,是不生效的。当然用 setTimeout
异步也不行。
没想到 iOS 那么严格,连输入框 focus
都要求用户真实点击,还不能是异步线程执行。
–END–