input.focus()失效

9,270 阅读2分钟

简介

主动调用 input.focus() 可以替用户主动锁定输入框并弹出输入法,引导用户填写表单。 但是在实际的使用中,页面有时并不会按照我们的预想去执行。本文将列举几个限制条件,并给出解决方案。

针对于下列问题场景,我写了一个简易的在线DEMO供大家亲自试验:点击查看

IOS Safari浏览器下必须由用户点击触发

在IOS Safari浏览器下要求 input.focus() 的调用必须位于onclick 的回调函数中,且同步执行,例如:


// 点击btnElement即可立即focus【有效】
btnElement.addEventListener('click', function() {
  input.focus()
})

反面教材一

在其他位置调用是无效的,例如:


// 页面加载完后立即focus:【IOS Safari浏览器下无效】
window.addEventListener('load', function() {
  input.focus()
})

如果你确实要实现这样的功能,可以尝试使用 autofocus 属性


// 页面加载完后立即focus:【有效】
Username: <input type="text" autofocus
 placeholder="来都来了就留下大名吧"
 />

autofocus可以在页面加载完成后自动focus相应的表单组件,但限制是一个页面只能有一个元素设置该属性。

反面教材二

在点击回调函数中异步调用是无效的,例如:


// 点击btnElement后异步调用focus【IOS Safari浏览器下无效】
btnElement.addEventListener('click', function() {
  setTimeout(() => input.focus(), 200)
})

常见的异步调用有 setTimeoutPromise。IOS Safari浏览器下,包裹其中的 input.focus() 都是无效的。

目标<input>元素必须可见

这里说的可见是指当 input.focus() 调用时要满足以下条件:

  1. 目标 <input> 元素已渲染到dom上
  2. 目标 <input> 元素不是 display: none;
  3. 目标 <input> 元素不是 visibility: hidden;

这种问题常见于调起一个弹框界面,其中元素恰好位于弹框内,而我们在元素还未可见就调用了 input.focus()。尤其是在React或者Vue等现代框架中,我们通过设置状态控制弹框的显隐,这种改变反应到dom上并不是同步的,例如:


const LoginModal: React.FC<{}> = () => {
  const [show, setShow] = useState(false)
  const ref = useRef<HTMLInputElement>(null)

  const handleClick = () => {
    // Modal弹框真正被渲染其实是异步发生的
    setShow(true)

    // 这个时候 input 很可能并未加载或者不可见 【无效】
    ref.current?.focus()
  }

  return (
    <div className="login-modal-comp">
      <button onClick={handleClick}>Show Modal</button>
      <Modal centered visible={show}>
        Username: <input ref={ref} type="text" placeholder="来都来了就留下大名吧" />
      </Modal>
    </div>
  )
}

如果这个恰好是你想要实现的功能,你同样可以借助 autofocus

<Modal centered visible={show}>
  Username: <input ref={ref} autoFocus
 type="text" placeholder="来都来了就留下大名吧" />
</Modal>
  • 注意在React中为驼峰命名:autoFocus

<input>元素会在重新出现时自动聚焦并拉起输入框,亲测有效。