Open5

iOS で自動フォーカス時に仮想キーボードが表示されない件の検証結果

Toshifumi ImanishiToshifumi Imanishi

時折、仮想キーボードを表示させるためにプログラムでフォーカスを制御するケースがあります。しかし、iOS のブラウザ(e.g. Mobile Safari)はプログラムで制御されたフォーカス時に仮想キーボードを表示しません。本挙動はバグではなく開発者(Apple)が意図的に行なっているかと思います。Apple は WebKit のバグチケット「Autofocus on text input does not show keyboard」に次のように言及しています。

We (Apple) like the current behavior and do not want programmatic focus to bring up the keyboard
我々 Apple は現在の挙動を好み、キーボードを表示させるプログラムのフォーカスを望んでいません。
https://bugs.webkit.org/show_bug.cgi?id=195884#c4

検証結果、次の仮説を立てます。

ユーザーの動作を伴うイベントハンドラからの focus() 実行は仮想キーボードが表示されます。ただし、フォーカスの対象要素が視覚的に非表示の場合この限りではありません。useEffect()setTimeout() のようなユーザーの動作を伴わないコールバック関数内での focus() 実行は仮想キーボードが表示されません。

Toshifumi ImanishiToshifumi Imanishi

検証 1. マウント時に自動フォーカスを発火させる方法

結果:❌ 仮想キーボードは表示されません。

useAutoFocus.ts
import { useEffect, useRef } from 'react';

export const useAutoFocus = <RefType extends HTMLElement>() => {
  const ref = useRef<RefType>(null);
  useEffect(() => {
      const node = ref.current;
      if (node) {
          node.focus();
      }
  }, []);

  return ref;
}
input.tsx
export const Input = () => {
  const { ref } = useAutoFocus<HTMLInputElement>();
  return (
    <input
      type="text"
      ref={ref}
    />
  )
}
Toshifumi ImanishiToshifumi Imanishi

検証 2. イベントハンドラで自動フォーカスを発火させる方法

結果:⭕️ 仮想キーボードは表示されます。

input.tsx
export const Input = React.forwardRef((props, ref) => {
  return (
    <input
      type="text"
      ref={ref}
    />
  )
})
index.tsx
import { createRef } from 'react';
import { Input } from 'input';

const Index = () => {
  const ref = useRef<HTMLInputElement>();
  const handleClick = () => {
    if (ref.current) ref.current.focus();
  }
  return (
    <Input ref={ref} />
    <button type="button" onClick={handleClick}>AUTO FOCUS</button>
  )
};
Toshifumi ImanishiToshifumi Imanishi

検証 3. デフォルトで非表示の要素に対してイベントハンドラで自動フォーカスを発火させる方法

結果:❌ 仮想キーボードは表示されません。

index.tsx
import { createRef, useState } from 'react';
import { Input } from 'input';

const Index = () => {
  const [isVisible, setVisibility] = useState(false);
  const ref = useRef<HTMLInputElement>();
  const handleClick = () => {
    setVisibility(true);
    if (ref.current) ref.current.focus();
  }
  return (
    <Input isVisible={isVisible} ref={ref} />
    <button type="button" onClick={handleClick}>AUTO FOCUS</button>
  )
};
Toshifumi ImanishiToshifumi Imanishi

検証 4. デフォルトで非表示の要素に対してハックで自動フォーカスを発火させる方法

結果:⭕️ 仮想キーボードは表示されます。

useAutoFocus.tsx
import { useRef } from 'react';

export const useAutoFocus = <RefType extends HTMLElement>() => {
  const ref = useRef<RefType>(null);
  const triggerAutoFocus = (timeout = 100) => {
      if (!ref.current) return;
      const __tempEl__ = document.createElement('input');
      __tempEl__.style.position = 'absolute';
      __tempEl__.style.top = (ref.current.offsetTop + 7) + 'px';
      __tempEl__.style.left = ref.current.offsetLeft + 'px';
      __tempEl__.style.height = '0';
      __tempEl__.style.opacity = '0';
      document.body.appendChild(__tempEl__);
      __tempEl__.focus();

      setTimeout(() => {
          if (!ref.current) return;
          ref.current.focus();
          ref.current.click();
          document.body.removeChild(__tempEl__);
      }, timeout);
  };
  return {
    ref,
    triggerAutoFocus,
  };
};
index.tsx
import { createRef, useState } from 'react';
import { Input } from 'input';

const Index = () => {
  const [isVisible, setVisibility] = useState(false);
  const {ref, triggerAutoFocus} = useAutoFocus<HTMLInputElement>();
  const handleClick = () => {
    setVisibility(true);
    triggerAutoFocus();
  };
  return (
    <Input isVisible={isVisible} ref={ref} />
    <button type="button" onClick={handleClick}>AUTO FOCUS</button>
  )
};

https://stackoverflow.com/questions/54424729/ios-show-keyboard-on-input-focus