input焦点跳动问题探索
前言
本来应该周末写完的组件调研input篇,不出意外的鸽了。在和室友相约卸载炉石拒绝浪费人生后,不想又投入到了金铲铲的怀抱,玩了一个周末的约德尔人,破口大骂sb才会再玩这个羁绊...
这篇,就算作Input组件调研的番外篇,就input在focus时,焦点的位置在前后跳动的问题进行一个整理。
start
focus到底是谁的小孩
之前,一直认为input
的focus
是由click
事件触发的,点一点,焦点就过去了。
直到接触input
组件之后,才发现事情并不简单,菜鸡程序员好像又发现了新大陆,先写个html
在input
上绑定需要监听的事件,附上打印结果。
<body> <div> <input type="text" value="11" id="ipt"> </div> <script> ipt.onmouseup = function (e) { console.log('up'); } ipt.onmousedown = function (e) { console.log('down'); } ipt.onfocus = function () { console.log('focus'); } ipt.onclick = function () { console.log('click'); } </script> </body> 复制代码
从console
中可以发现,事件触发的顺序mousedown
- focus
- mouseup
- click
。
focus
完了,click
才姗姗来迟,足以证明孩子不是它的,是我让他喜当爹了。从结果中来看,focus
事件只能是由mousedown
触发的。
再做个亲子鉴定,给mousedown
加上一个阻止默认事件,再点击看看结果,发现确实focus
不触发了,证明之前的推论是正确的。
ipt.onmousedown = function (e) { e.preventDefault() console.log('down'); } 复制代码
input焦点位置异常。
这个其实很煎熬,因为我查了很多资料,也没有明白它的具体机制是怎么样的,只能通过举例子来说明一下。
1.有默认值的input初次调用focus
<body> <div> <input type="text" value="11111" id="ipt"> </div> <div> <button id="btn">test</button> </div> <script> btn.onclick = function() { ipt.focus(); } </script> </body> 复制代码
这个时候点击test
按钮,光标位置会出现在首格。
解决方法也很简单,先把value
置为空,focus
之后,再把value
放回去就可以了(setAttribute不行)
。
btn.onclick = function() { ipt.value = ''; ipt.focus(); ipt.value = '11'; } 复制代码
如果用的是autoFocus
属性,延迟赋值就可以解决了。
更改type
后,手动focus
输入框
p.onclick = function (e) { const t = ipt.getAttribute('type'); ipt.type = t === 'password' ? 'text' : 'password'; ipt.focus(); ipt.setAttribute('type', t === 'password' ? 'text' : 'password') } 复制代码
这种情况在原生中,其实只要吧focus()
和setAttribute
调换下位置,光标就会正常显示在最后了。
但是在react
中,state
都是延缓更新的,focus
一般都会运行在前面,所以一种办法就是放到setState
的回调函数中去调用focus
。
另一种就是阻止p
的mouseup
的默认事件。
p.onmouseup = function (e) { console.log('up'); e.preventDefault(); } 复制代码
input
在focus
的状态下时,通过点击事件触发type
的修改,或者触发setAttribute('value')
的修改时(不包含ipt.value = ***')
。input
光标就会前移到首位,解决方案就是阻止mouseup
的默认事件。原理是啥,我也不知道= =,希望懂的大哥们可以告诉我一下。
在react中解决焦点问题
遇到的情况就是在input
组件中新加password
组件,通过点击小眼睛,来控制type
的变更,达到显示和隐藏密码的效果。由于icon
不属于input
,所以正常点击的话会引起失焦。一般有2种方法来解决这个问题。
1.重新手动聚焦 -- 使用setState
的回调函数,或者useEffect
changeType(e) { this.setState({ type: (this.state.type === 'text' ? 'password' : 'text') }, () => { // focus光标在最后 this.inputRef.current.focus(); }); // focus光标在最前 // this.inputRef.current.focus(); } 复制代码
这里就举一个class
写法的例子。如果使用hook
写法useEffect
的话,需要加个标记位判断更新时候才运行,不然挂载时候就会自动focus
,官网上的hook
教程中有写怎么操作。
但是手动focus
会一个问题,因为组件中是可以传递自定义的onBlur
和onFocus
的,这种情况下,就会重新运行这2个函数,需要根据具体使用情况来判断是否合理。
使用e.preventDefault()
阻止默认事件
这也是各个组件库都使用的方法。 通过在icon
上绑定mouseup
和mousedown
事件,通过e.preventDefault()
阻止失焦和焦点跳动的问题。使焦点始终保持在input
当前的位置。
<Icon className="zent-input-icon" type={icon} onMouseUp={preventDefault} onMouseDown={preventDefault} onClick={onIconClick} /> 复制代码
end
起因是在新增password
组件时候,发现了焦点前移的问题。然后在使用useEffect
后,又看了各个组件库大佬们写的代码,发现了通过mouseup
和mousedown
解决问题的办法,就来小小研究了一下,补一补基础。
input
组件的调研篇也会尽量在这周写完。依旧是靠阅读大佬们的代码,寻求进步的一天~
作者:懒狗小前端
链接:https://juejin.cn/post/7030996134575226894