IME(全角)入力におけるjsイベント現状調査

この記事で対象としているブラウザはChrome(mac/win), Safari(mac), Firefox(mac/win), Edge(win) の最新バージョンで、以降これらをモダンブラウザと表記する。


tl;dr



  • Safariで全角入力におけるJSイベントの挙動がおかしかったため調査したところ、仕様(draft含む)上はInputEventだけで判定できそうで期待したが、現状の挙動はベンダー毎に異なった


    • 本命のinputEvent.isComposingはSafari/Edgeで対応してなかったりChrome/Firefoxでも挙動が違って使い辛い


    • inputEvent.inputTypeは使える場面がありそう




  • CompositionEventはモダンブラウザ全てに実装されており最も有用



    • inputイベントと組み合わせる場合はFirefoxのcompositionendイベントの発火順に注意




  • KeyboardEvent.isComposingはEdge以外のモダンブラウザで実装され、特にkeyupイベントでの値は各ブラウザで揃っていた


仕様

UI EventsのW3C Working Draft(8 November 2018)の入力とイベント発火の表が非常にわかりやすい

ただ標準仕様に対するベンダーの実装が必ずしも仕様に則しきれていないのは2019年も相変わらず。


InputEvent


  • 仕様上はinputTypeisComposingプロパティでIME入力状態を判別可能


    • ただしisComposingはSafari未実装

    • そしてChrome/Firefoxで挙動が異なる(下記参照)



  • inputTypeのENUM値はInput Events Level 2の仕様(draft)を参考


  • dataプロパティでマルチバイト判定する手もあるが、現状Safari/Chromeのみ


モダンブラウザのinputType/isComposing/data


  • 入力に対するinputType(string)/isComposing(boolean)/data(string)の値を記載



    • -はundefined



  • Edge(win)はどちらのプロパティもないため省略

入力
Chrome
Firefox
Safari

1(全角、未確定)
'insertCompositionText'/true/'1'
'insertCompositionText'/true/-
'insertCompositionText'/-/'1'

Enter(上記確定)
'insertCompositionText'/true/'1'
'insertCompositionText'/false/-1

'deleteCompositionText'/-/null

'insertFromComposition'/-/'1'

2(半角)
'insertText'/false/'2'
'insertText'/false/-
'insertText'/-/'2'


  • 全角入力がinputType: 'insertCompositionText'で半角入力がinputType: 'insertText'であることは共通

  • Enter押下での確定には一貫性がない


    • compositionendイベント発火前にinputイベントが発火した場合にisComposing: true。つまりChromeとFirefoxとでcompositionendの発火タイミングが異なる(後述)

    • safariはinputイベント発火数が他のブラウザより多い




  • マウスで確定させた場合は、Chrome/Safariでは確定時にinputイベントは発火しない一方、FirefoxではEnterと同じ値で発火した


CompositionEvent

IMEによるテキスト編集監視に用いるイベント。半角入力時は当然発火しない。


KeyboardEvent

キーボード操作、keydown/keyupイベントで監視



  • if (KeyboardEvent.keyCode === 229)でIME入力を判定しているスクリプトを見かけるがKeyboardEvent.keyCodeはDeprecated

  • Enter押下を検知して力技で......


    • IME確定以外でのEnter押下を区別できるのか疑問

    • ライブ変換とか確定に必要なEnter回数はIMEの設定によって異なる気がする

    • マウスからの確定は当然検知できない

    • とはいえIEサポートしたいならこれしかない?




  • KeyboardEvent.isComposingの実装はEdge以外は済んでいる


    • このbool値の仕様はinputEvent.isComposing同様、compositionstartとcompositionendの間がtrueでそれ以外はfalse

    • しかしSafariだけ下表の通り異なる

    • だがkeyupは揃っており、判定には有益

    • KeyboardEventの発火順は各ブラウザともW3Cの仕様に即している




入力に対するイベント発火の流れ


  • 値はEvent.type

  • 太字はisComposingがtrue

  • Edgeでのマウスクリック確定の方法がわからず未調査

入力
Chrome
Firefox
Safari
Edge

1(全角、未確定)
keydown
keydown
keydown
keydown

compositionstart
compositionstart
compositionstart
compositionstart

compositionupdate
compositionupdate
compositionupdate
compositionupdate

input
input
input
keyup

keyup
keyup
keyup
input

Enter押下で確定
keydown
keydown
keydown
compositionend

compositionupdate
compositionend
input

input
input
input

compositionend
keyup
compositionend

keyup

keyup

マウスクリックで確定
compositionend
compositionend
compositionend
?

input1

?


その他の手段


changeイベント


  • 入力確定後のEnter押下やfocusが外れた時に発火

  • 入力時にリアルタイムに処理したい場合には使えない


Appendix


調査スクリプト


  • 単純に各イベントリスナーにconsole.logを入れるだけだとダメ


    • ロギング順と発火順が同じとは限らない



  • 各種Eventオブジェクトのtimestampを見てsortして表示


check_inputEvent.html

<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
<body>
<input id="x"/>
<button id="y">out</button>
<script>
var stores = [];
var store = ({timeStamp, type, isComposing, inputType, data, key}) => {
stores.push({timeStamp, type, isComposing, inputType, data, key});
};
var x = document.getElementById('x');
var y = document.getElementById('y');
x.addEventListener('input', store);
x.addEventListener('keyup', store);
x.addEventListener('keydown', store);
x.addEventListener('compositionstart', store);
x.addEventListener('compositionupdate', store);
x.addEventListener('compositionend', store);
y.addEventListener('click', (e) => {
console.table(stores.sort((a, b) => a.timeStamp > b.timeStamp));
});
</script>


調査背景


  • 入力値に対してフォーマットした値を再代入するJSを書いた


    • 例えば、郵便番号入力欄で、全角半角問わずリアルタイムに3桁目と4桁目の間にハイフンを補ってあげる処理



  • Safariのみ全角入力時に重複入力される現象が起きた


    • 下記は全角1234と入力した場合の挙動



safari_bug.gif

<!DOCTYPE html>

<html>
<head>
<meta charset="utf-8">
<body>
<input id="x"/>
<script>
var formatFn = (v) => v.replace(/^([^-]{3})([^-]{1,})/, '$1-$2');
var inputElement = document.getElementById('x');
inputElement.addEventListener('input', (e) => {
e.target.value = formatFn(e.target.value);
});
</script>

全角入力は確定までフォーマットしない、という方針なら、例えばinputイベント部分を下記のようにすればモダンブラウザ対応可能

let isComposing = false;

inputElement.addEventListener('compositionstart', (e) => {
isComposing = true;
});
inputElement.addEventListener('input', (e) => {
if (!isComposing) {
e.target.value = formatFn(e.target.value);
}
});
inputElement.addEventListener('compositionend', (e) => {
e.target.value = formatFn(e.target.value);
isComposing = false;
});

またはinputTypeをみる(Edgeでは常にundefinedだが重複入力問題はSafariのみ)

inputElement.addEventListener('input', (e) => {

if (e.inputType !== 'insertCompositionText') {
e.target.value = formatFn(e.target.value);
}
});


イベントリスナーの確認

何故Safariだけ上記バグがあるのか、Safari(を含むモダンブラウザ)のコンソールではgetEventListeners(node)というAPIが使えるので調べて見たが、特に異常はなかった。

safari_api.png

またmonitorEvents(document)2で発火イベントを全体的に監視してみた。SafariではtextInputイベントが全角確定時の一度だけ発火し、Chromeではinputイベントが全角入力の度に発火していた。

(念の為inputElement.removeEventListener('textInput', listener);してみたが効果なし)