前言
本来应该周末写完的组件调研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
组件的调研篇也会尽量在这周写完。依旧是靠阅读大佬们的代码,寻求进步的一天~