input焦点跳动问题探索

2,146 阅读4分钟

前言

本来应该周末写完的组件调研input篇,不出意外的鸽了。在和室友相约卸载炉石拒绝浪费人生后,不想又投入到了金铲铲的怀抱,玩了一个周末的约德尔人,破口大骂sb才会再玩这个羁绊...

这篇,就算作Input组件调研的番外篇,就input在focus时,焦点的位置在前后跳动的问题进行一个整理。

start

focus到底是谁的小孩

之前,一直认为inputfocus是由click事件触发的,点一点,焦点就过去了。

直到接触input组件之后,才发现事情并不简单,菜鸡程序员好像又发现了新大陆,先写个htmlinput上绑定需要监听的事件,附上打印结果。

<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>

image.png

console中可以发现,事件触发的顺序mousedown - focus - mouseup - click

focus完了,click才姗姗来迟,足以证明孩子不是它的,是我让他喜当爹了。从结果中来看,focus事件只能是由mousedown触发的。

再做个亲子鉴定,给mousedown加上一个阻止默认事件,再点击看看结果,发现确实focus不触发了,证明之前的推论是正确的。

ipt.onmousedown = function (e) {
    e.preventDefault()
    console.log('down');
}

image.png

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>

image.png

这个时候点击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

另一种就是阻止pmouseup的默认事件。

p.onmouseup = function (e) {
    console.log('up');
    e.preventDefault();
}

inputfocus的状态下时,通过点击事件触发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会一个问题,因为组件中是可以传递自定义的onBluronFocus的,这种情况下,就会重新运行这2个函数,需要根据具体使用情况来判断是否合理。

使用e.preventDefault()阻止默认事件

这也是各个组件库都使用的方法。 通过在icon上绑定mouseupmousedown事件,通过e.preventDefault()阻止失焦和焦点跳动的问题。使焦点始终保持在input当前的位置。

<Icon
  className="zent-input-icon"
  type={icon}
  onMouseUp={preventDefault}
  onMouseDown={preventDefault}
  onClick={onIconClick}
/>

end

起因是在新增password组件时候,发现了焦点前移的问题。然后在使用useEffect后,又看了各个组件库大佬们写的代码,发现了通过mouseupmousedown解决问题的办法,就来小小研究了一下,补一补基础。

input组件的调研篇也会尽量在这周写完。依旧是靠阅读大佬们的代码,寻求进步的一天~