この記事で対象としているブラウザは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)の入力とイベント発火の表が非常にわかりやすい
- en: https://www.w3.org/TR/uievents/#keys-IME
- ja: https://triple-underscore.github.io/uievents-ja.html#keys-IME
ただ標準仕様に対するベンダーの実装が必ずしも仕様に則しきれていないのは2019年も相変わらず。
InputEvent
- 仕様上はinputTypeとisComposingプロパティで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イベント発火数が他のブラウザより多い
- compositionendイベント発火前にinputイベントが発火した場合に
- マウスで確定させた場合は、Chrome/Safariでは確定時にinputイベントは発火しない一方、FirefoxではEnterと同じ値で発火した
CompositionEvent
IMEによるテキスト編集監視に用いるイベント。半角入力時は当然発火しない。
- compositionstart: IMEで未確定文字列の入力を開始した時
- compositionupdate: IMEで編集中のテキストが変更された時
- compositionend: 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の仕様に即している
- このbool値の仕様は
入力に対するイベント発火の流れ
- 値は
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して表示
<!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と入力した場合の挙動
<!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が使えるので調べて見たが、特に異常はなかった。
またmonitorEvents(document)
2で発火イベントを全体的に監視してみた。SafariではtextInput
イベントが全角確定時の一度だけ発火し、Chromeではinput
イベントが全角入力の度に発火していた。
(念の為inputElement.removeEventListener('textInput', listener);
してみたが効果なし)