IME 入力のブラウザ間での挙動の違いを調査したのでその結果をまとめます。
IME(Input Method Editor)
IME はテキスト入力をサポートするソフトウェアです。
かな入力時に(漢字などの)変換候補を表示する機能などを提供しています。
IME 入力は、日本語話者なら必ず使っているであろう機能であり、アジア圏のユーザを対象にしている Web アプリケーション内で、テキスト入力を制御したい場合は、IME 入力を制御する JavaScript コードを書く必要があるかもしれません。
JavaScript で IME 入力を制御する際には、以下のような点に注意が必要です。
- ブラウザ間で、IME 入力に関係する Event の発生の順番が異なる場合があります。
- 同じブラウザでもバージョンにより Event の発生の順番が異なる場合があります。
- 同じブラウザで同じバージョンでも、入力エリアが
textarea
かinput
かcontenteditable
かで Event のプロパティの値に違いがある場合がある。
IME 入力を制御するコードを書く前に、ブラウザ間での IME 入力の挙動の違いを理解することは重要です。
調査対象
さまざまな OS、ブラウザ、入力エリア、Event を対象に IME 入力時の挙動を比較しました。調査対象を以下にまとめています。
OS
MacOS および Windows の環境での調査を行っています。
- macOS Ventura 13.0.1 (MacBook Air - Apple M1 2020)
- macOS Sonoma 14.1.2(MacBook Air - Apple M1 2020)
- Windows 10 (DynaBook)
MacOS は 2 つのバージョンでの調査を行っていますが、inputEvent.isComposing
プロパティの値の違い以外同じであったため、基本的に 2 つの結果を分けずにまとめて書いています。
ブラウザ
以下が、調査対象のブラウザとそのバージョンです。
-
Microsoft Edge
(mac / win)- macOS
- version: 115.0.1901.203
- version: 120.0.2210.61
- windows
- version: 95.0.1020.40
- version: 120.0.2210.61
- macOS
-
Google Chrome
(mac / win)- macOS
- version: 116.0.5845.140
- version: 120.0.6099.71
- windows
- version: 101.0.4951.54
- version: 120.0.6099.71
- macOS
-
Mozilla Firefox
(mac)- macOS
- version: 120.0.1
- macOS
-
Safari
(mac)- macOS
- version: 16.1
- version: 17.1.2
- macOS
Internet Explorer
はサポートが既に終了しているので調査の対象としていません。
入力エリア
入力エリアが、以下の3つの場合に分けて調査しました。
-
textarea
タグ (<textarea>
) -
input
タグ (<input>
) -
contenteditable
属性がtrue
の要素
Document.designMode
はあまり一般的な機能ではないと思ったので、調査の対象から外しました。
本記事では input
と表記していたら、input
イベントを指します。<input>
は input
タグと表記します。
Event
今回調査した Event は以下の 7 つです。
-
beforeinput
: 要素の値が変更されようとしているときに発生 -
input
: 要素の値 (value) が変更されたときに発生 -
keydown
: キーが押されたときに発生 -
keyup
: キーが離されたときに発生 -
compositionstart
: IME などのテキスト変換システムが新しい変換セッションを開始した時に発生 -
compositionupdate
: IME などのテキスト変換システムによって制御されているテキスト変換セッションに新しい文字が入力されたときに発生 -
compositionend
: IME などのテキスト編集システムが現在の編集セッションを完了またはキャンセルした時に発生
keypress
は非推奨であるため、change
は、IME 入力時のリアルタイム性に欠けるため、調査の対象としていません。
参考: - Element: keypress イベント - Web API | MDN - IME(全角)入力における js イベント現状調査 - Qiita
調査用コード
以下が調査に使用したコードです。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<input type="text" id="input">
<br>
<textarea id="textarea"></textarea>
<br>
<div id="contenteditable" contenteditable="true"><p><br></p></div>
</div>
<script>
const input = document.querySelector('#input');
addEvent(input);
const textarea = document.querySelector('#textarea');
addEvent(textarea);
const contenteditable = document.querySelector('#contenteditable');
addEvent(contenteditable);
function addEvent(editor){
if(
!(editor instanceof HTMLTextAreaElement) &&
!(editor instanceof HTMLInputElement) &&
!(editor instanceof HTMLDivElement)
) return;
// InputEvent
editor.addEventListener("beforeinput", function(e) {
console.log(`type: ${e.type}, data: ${e.data}, isComposing: ${e.isComposing}`);
});
editor.addEventListener("input", function(e) {
console.log(`type: ${e.type}, data: ${e.data}, isComposing: ${e.isComposing}`);
});
// KeyboardEvent
editor.addEventListener("keydown", function(e) {
console.log(`type: ${e.type}, key: ${e.key}, isComposing: ${e.isComposing}`);
});
editor.addEventListener("keyup", function(e) {
console.log(`type: ${e.type}, key: ${e.key}, isComposing: ${e.isComposing}`);
});
// CompositionEvent
editor.addEventListener("compositionstart", function(e) {
console.log(`type: ${e.type}, data: ${e.data}`);
});
editor.addEventListener("compositionupdate", function(e) {
console.log(`type: ${e.type}, data: ${e.data}`);
});
editor.addEventListener("compositionend", function(e) {
console.log(`type: ${e.type}, data: ${e.data}`);
});
}
</script>
</body>
</html>
-
contenteditable
の要素は、Quill や Lexical などの WYSIWYG ライブラリの初期状態を参考に<div id="contenteditable" contenteditable="true"><p><br></p></div>
としています。 -
addEvent
関数で以下のプロパティをコンソールに表示します。-
InputEvent (
beforeinput
,input
)- type: イベントの種別を表す文字列
- data: 挿入された文字
-
isComposing:
compositionstart
とcompositionend
の間に発生したかを示す値
-
KeyboardEvent (
keydown
,keyup
)- type: イベントの種別を表す文字列
- key: ユーザーが押したキーの値
-
isComposing:
compositionstart
とcompositionend
の間に発生したかを示す値
-
CompositionEvent (
compositionstart
,compositionupdate
,compositionend
)
-
InputEvent (
調査結果
要点
すでに文章が長くなり始めているので先に重要な点を簡単にまとめます。
- 最新のブラウザでは、KeyboardEvent を除くと、
compositionupdate
->beforeinput
->input
の順で Event が発生している。 - IME 終了時の Event の発生の順番は、多くパターンがあり、InputEvent や KeyboardEvent が発生せずに
compositionend
しか発生しないパターンもある。 -
KeyboardEvent
は IME 入力(を含むテキスト入力処理)の制御に向いていない。テキストの入力を処理したい場合は、代わりに input イベントを使用してください。ユーザーが他の種類のテキスト入力、例えば、タブレット端末やタブレット機器による手書き入力システムなどを使用している場合、キーボードイベントが発生することはありません。
KeyboardEvent - Web API | MDN -
input
のプロパティの値は、contenteditable 要素では扱いづらい。 - バージョン 16.4 以降の Safari では
inputEvent.isComposing
がサポートされており、IME 入力中かの判断が容易になった。 -
Microsoft Edge
は バージョン 79 からGoogle Chrome
と同じ Chromium ベースに移行しため、Microsoft Edge
とGoogle Chrome
は同じような挙動をする。
非 IME (ローマ字入力)
IME 入力の Event を調査する前に、まずローマ字入力に焦点を当てます。
- ローマ字入力ではブラウザ間やバージョン間で差異がなく、以下の順番で Event が発生する。
keydown
beforeinput
input
keyup
- ただし、長押ししている間は
keyup
が発生しない。keydown
beforeinput
input
-
keydown
: 長押ししている間、keydown が発生し続ける。 -
keyup
: キーが離されるとはじめて発生する。
- 以下の場合、
input
が発生しない。- 削除する対象がないのに、
Backspace
やDelete
、Control + D
- input タグ内で
Enter
もしくはShift + Enter
- 削除する対象がないのに、
- 以下の場合、Safari は
beforeinput
も発生しない(Chrome と Edge、Firefox は発生する)- 削除する対象がないのに、
Backspace
やDelete
、Control + D
- input タグ内で
Enter
もしくはShift + Enter
- 削除する対象がないのに、
-
isComposing
はInputEvent
、KeyboardEvent
いずれもfalse
-
InputEvent
のdata
は入力値(基本的にbeforeinput
とinput
で同じ) -
Backspace
やBackspace + fn
、Control + D
、Enter
、Shift + Enter
の場合は、InputEvent
のdata
の値はどれもnull
InputEvent
の inputType
プロパティを使用することで、null
の識別ができます。
以下は、inputType
の値の、input タグ、textarea ダグ、contenteditable 要素での違いをまとめたものです。
※ Backspace
、 Delete
、Control + D
は削除する対象があることを前提としています。(削除する対象がない場合は上記で示したように Event 自体が発生しないことがあります。)
※ 特に分けて書いていない限り、beforeinput
と input
の inputType
の値は同じです。表内では、beforeinput イベントは BI、input イベントは I と表記しています。
KeyboardEvent.key | input タグ | textarea ダグ | contenteditable |
---|---|---|---|
Backspace | deleteContentBackward | deleteContentBackward | deleteContentBackward |
Delete (※1) | deleteContentForward | deleteContentForward | deleteContentForward |
Control + d | deleteContentForward | deleteContentForward | deleteContentForward |
Enter (Edge, Chrome, Firefox) |
BI: insertLineBreak I: 発生しない |
insertLineBreak | insertParagraph(※2) |
Enter (Safari) |
BI: 発生しない I: 発生しない |
insertLineBreak | insertParagraph |
Shift + Enter (Edge, Chrome, Firefox) |
BI: insertLineBreak I: 発生しない |
insertLineBreak | insertLineBreak(※2) |
Shift + Enter (Safari) |
BI: 発生しない I: 発生しない |
insertLineBreak | insertParagraph |
※1 Mac のキーボード場合は Backspace + fn
、Dynabook のキーボードの場合は DEL
※2 Edge と Chrome では、input
イベントの inputType が insertText
になる場合がある。(文字入力後、間隔を空けずに Enter (もしくは Shift + Enter) を押すと insertText になる。その後、続けて Enter (もしくは Shift + Enter) を押す場合も insertText になる。)
ローマ字入力まとめ
以上のように、ローマ字入力に関しても、ブラウザ間での違いが見受けられます。
テキスト入力時はブラウザ間でイベントの発生順に違いはないですが、Enter
やShift + Enter
のキー入力時やテキスト削除時では、イベントが発生しなかったり、発生した場合でもプロパティの値が異なることがあります。
ローマ字入力の制御をクロスブラウザ対応する場合、input タグや textarea ダグでは input
イベントと beforeinput
イベントの両方が使えそうですが、contenteditable 内では input
イベントの inputType は信用ができず、beforeinput
イベントで制御するのが適切なようです(Safari では contenteditable 内で Enter
とShift + Enter
の違いを識別する場合は KeyboardEvent イベントの利用も必要)。
IME
本題の IME 入力に焦点を当てます。
ローマ字入力との違い
- IME 入力では CompositionEvent (
compositionstart
,compositionupdate
,compositionend
) が発生します。 - compositionstart と compositionend の間に発生した KeyboardEvent (keydown, keyup) と InputEvent (beforeinput, input) イベントの isComposing プロパティは
true
が返されます。(バージョンが 16.4 よりも前の Safari の場合は InputEvent の isComposing プロパティはundefined
) - ローマ字入力と同様に keydown 長押ししている間、keydown が発生し続けますが、ローマ字入力は 1 つ目の Event のみが入力エリアに反映される一方、IME 入力では 2 つ目以降の Event も入力エリアに反映されます(例えば、a キーを押し続けると、
ああああああああああ....
となる)。 - InputEvent の data プロパティの値は、ローマ字入力の場合は入力文字(a, 1, k など)と一致しますが、IME 入力は IME 入力中のすべての文字(か, 100, 下記など)を含んでいます。
IME 入力の Event の発生の順番
IME 入力の Event の発生の順番を一度に理解するのは難しいので 3 つのフェーズに分けて説明します。
- IME 入力開始時
- IME 入力中
- IME 入力終了時
また、ローマ字入力の際と同様に keyup
はキーが離されるとはじめて発生するので、キーを押し続けていると、次の文字の keydown イベントの方が先に発生します。このため、本調査は、他のキーを押す前に現在押しているキーを離しているという前提を基本としています。
表内では、イベントはそれぞれ以下のように省略して表記しています。
- keydown: KD
- keyup: KU
- compositionstart: CS
- compositionupdate: CU
- compositionend: CE
- input: I
- beforeinput: BI
IME 入力開始時
かな入力ができる状態で、キーボードの文字を入力すると IME 入力が開始します。
Windows
まずは、Windows の Edge と Chrome を見ていきます。
IME 入力で k
を押すと以下のような結果になります。
()内は、InputEvent.data もしくは KeyboardEvent.key または CompositionEvent.dataの値を表しています。
Edge (ver.95) |
Edge (ver.120) |
Chrome (ver.101) |
Chrome (ver.120) |
---|---|---|---|
KD(Process) | KD(Process) | KD(Process) | KD(Process) |
CS() | CS() | CS() | CS() |
BI(k) | CU(k) | BI(k) | CU(k) |
CU(k) | BI(k) | CU(k) | BI(k) |
I(k) | I(k) | I(k) | I(k) |
KU (Process) | KU (Process) | KU (Process) | KU (Process) |
KU (k) | KU (k) | KU (k) | KU (k) |
- Edge(ver.95) と Chrome(ver.101) はイベントの順番が同じです。また、Edge(ver.120) と Chrome(ver.120) のイベントの順番も同じです。
- ブラウザの種類やバージョンに関係なく、
keydown
が最初に発生します。data プロパティの値はProcess
で、isComposing プロパティの値はfalse
です。 -
compositionstart
以降の KeyboardEvent (keydown, keyup) と InputEvent (beforeinput, input) イベントの isComposing プロパティはtrue
が返されます。 - Edge と Chrome の新旧バージョンでそれぞれ
beforeinput
とcompositionupdate
の発生の順番が入れ替わっています。 -
keyup
は2回呼ばれますが、それぞれの key プロパティはProcess
と入力したキー
です。
MacOS
Edge (ver.115) Chrome (ver.116) |
Edge (ver.120) Chrome (ver.120) |
Firefox (ver.120) | Safari (ver.17.1.2) |
---|---|---|---|
KD(k) | KD(k) | KD(Process) | CS() |
CS() | CS() | CS(<empty string> ) |
CU(k) |
BI(k) | CU(k) | CU(k) | BI(k) |
CU(k) | BI(k) | BI(k) | I(k) |
I(k) | I(k) | I (k) | KD(k) |
KU(k) | KU(k) | KU(k) | KU(k) |
- Windows との比較 (Edge と Chrome)
- Windows の Edge や Chrome と異なり、
keydown
では、data プロパティの値は入力したキー
です。 - Windows の Edge や Chrome と同じように、Edge と Chrome の新旧バージョンでそれぞれ
beforeinput
とcompositionupdate
の発生の順番が入れ替わっています。 - Mac の Edge と Chrome は
keyup
は 1 回しか呼ばれません。data プロパティの値は入力したキー
です。
- Windows の Edge や Chrome と異なり、
- Firefox
- Edge や Chrome の新バージョンと同じ順序で Event が発生しますが、
keydown
イベントでは、data プロパティの値はProcess
です。 -
compositionupdate
の data プロパティの値は空文字でなく、<empty string>
です。
- Edge や Chrome の新バージョンと同じ順序で Event が発生しますが、
- Safari
- KeyboardEvent を除けば、Edge や Chrome の新バージョン、Firefox と同じ順序で Event が発生しますが、
keydown
がinput
の後に回されています。 -
バージョンが 16.4 よりも前の Safari の場合は InputEvent の isComposing プロパティの値は
undefined
になります。
- KeyboardEvent を除けば、Edge や Chrome の新バージョン、Firefox と同じ順序で Event が発生しますが、
IME 入力中
IME 入力中つまり「IME 入力開始後で、IME 入力が確定するまでの間」の Event の発生の順番を見ていきます。
具体的には以下の 3 つのケースが想定されます。
- 次の文字が入力される(例: あ -> あい)
- 予測された単語の選択(例: あい -> 愛)
- 文字を削除(例: あい -> あ)
※ 文字を全て削除するとIME入力が終了してしまう(例: あ -> )
どのケースも以下の順番で Event が発生します。
k
キーを押し、IME 入力開始した後、 a
キーを押した際の結果です。
Windows Edge (ver.120) Chrome (ver.120) |
MacOS Edge (ver.120) Chrome (ver.120) |
Firefox (ver.120) | Safari (ver.17.1.2) |
---|---|---|---|
KD(Process) | KD(a) | KD(Process) | CU (か) |
CU(か) | CU(か) | CU(か) | BI(か) |
BI(か) | BI(か) | BI(か) | I(か) |
I(か) | I(か) | I(か) | KD(a) |
KU(Process) | KU(a) | KU(a) | KU(a) |
KU(a) |
- 旧バージョンの Edge と Chrome は
beforeinput
とcompositionupdate
の発生の順番が入れ替わっているだけなので表にはまとめていません。 - バージョンが 16.4 よりも前の Safari の場合は InputEvent の isComposing プロパティの値は
undefined
になります。 - KeyboardEvent を除けば、すべてのブラウザで
compositionupdate
->beforeinput
->input
の順番で Event が発生します。
IME 終了時
IME 終了時つまり「IME 入力が確定する時」の Event の発生の順番を見ていきます。
具体的には以下の 4 つのケースが想定されます。
- Enter キーをクリックして IME 入力を確定
- IME 入力中に入力中の文字を全て削除(例: あ -> )
- IME 入力中に画面をクリック
- 予測された単語の選択中に他のキーをクリック
Enter キーをクリックして IME 入力を確定
以下は、k
キーを押し、IME 入力開始した後、さらに a
キーを押し、IME によってか
に変換された状態で Enter キーをクリックした場合の結果です。
Windows Edge (ver.120) Chrome (ver.120) |
MacOS Edge (ver.120) Chrome (ver.120) |
Firefox (ver.120) | Safari (ver.17.1.2) |
---|---|---|---|
KD(Process) | KD(Enter) | KD(Process) | BI (null) |
CU(か) | CU(か) | BI(か) | I (null)) |
BI(か) | BI(か) | CE(か) | BI(か) |
I(か) | I(か) | I(か) | I(か) |
CE(か) | CE(か) | KU (Enter) | CE(か) |
KU(Process) | KU(Enter) | KD(Enter) | |
KU(Enter) |
Enter キーの関連情報
Windows では、予測された単語の選択中に Enter キーを 1 度クリックするとIMEが確定します。
しかし、macOS では Enter キーを 2 度クリックする必要がある場合があります。その場合、1 度目のクリックでは以下の順で 2 つの Event が発生します。
key | isComposing | |
---|---|---|
KD | Enter | true |
KU | Enter | true |
IME 入力中の文字を全て削除
以下は、k
キーを押し、IME 入力開始した後、さらに a
キーを押し、IME によってか
に変換された状態で Backspace
キーをクリックした場合の結果です。
Windows Edge (ver.120) Chrome (ver.120) |
MacOS Edge (ver.120) Chrome (ver.120) |
Firefox (ver.120) | Safari (ver.17.1.2) |
---|---|---|---|
KD(Process) | KD(Backspace) | KD(Process) | BI (null) |
CU() | CU() | CU() | I(null) |
BI() | BI() | BI() | CE() |
I(null) 【※1】 | I(null) 【※1】 | CE() | KD(Backspace) |
CE() | CE() | I(か) | KU(Backspace) |
KU(Process) | KU(Backspace) | KU(Backspace) | |
KU(Backspace) |
※1 Edge と Chrome で contenteditable 要素を場合は、注意が必要です。
- 1 つのキーだけ(例: k, あ, 1) をクリック後、IME 入力中の 文字を全て消す
->input
のdata
プロパティの値はnull
になる。 - 2 つのキー以上(例: か, 12) をクリック後、IME 入力中の 文字を全て消す
->input
のdata
プロパティの値は削除直前の文字
になる。
これは、Backspace
以外に Escape
を使い、IME 入力中の 文字を全て削除した場合も同じです。
Windows 用の Edge (ver.120) と Chrome (ver.120) で、DEl(Delete)キー使用してIME 入力中の文字を全て削除すると、Backspace キーの場合と同じ順番で Event が発生する。
最後の、keyup の key の値は、Backspace
ではなく Delete
です。
以下は、k
キーを押し、IME 入力開始した後、さらに a
キーを押し、IME によってか
に変換された状態で Escape
キーをクリックした場合の結果です。
Windows Edge (ver.120) Chrome (ver.120) |
MacOS Edge (ver.120) Chrome (ver.120) |
Firefox (ver.120) | Safari (ver.17.1.2) |
---|---|---|---|
KD(Process) | KD(Escape) | KD(Process) | CU() |
CU() | CU() | CU() | BI() |
BI() | BI() | BI() | I() |
I(null) 【※1】 | I(null) 【※1】 | I() | BI(null) |
CE() | CE() | CU() | I(null) |
KU(Process) | KU(Escape) | BI() | CE() |
KU(Escape) | CE() | KD(Escape) | |
I() | KU(Escape) | ||
KU(Escape) |
※1 Edge と Chrome で contenteditable 要素を場合は、注意が必要です。
- 1 つのキーだけ(例: k, あ, 1) をクリック後、IME 入力中の 文字を全て消す
->input
のdata
プロパティの値はnull
になる。 - 2 つのキー以上(例: か, 12) をクリック後、IME 入力中の 文字を全て消す
->input
のdata
プロパティの値は削除直前の文字
になる。
これは、Escape
以外に Backspace
を使い、IME 入力中の 文字を全て削除した場合も同じです。
Escape キーの関連情報
-
予測された単語の選択中の場合に Escape キーを 1 度クリックすると、選択前の文字に戻ります。(例: 川 -> かわ)
Windows の場合は、2 度クリックする必要がある場合がある。 -
予測された単語の選択中でない場合、IME 入力中の文字は全て消えます。
IME 入力中に画面をクリック
以下は、k
キーを押し、IME 入力開始した後、さらに a
キーを押し、IME によってか
に変換された状態で画面をクリック
した場合の結果です。クリックした場所によって結果が変わる場合があります。
- Web ページ上の要素をクリック
Windows Edge (ver.120) Chrome (ver.120) |
MacOS Edge (ver.120) Chrome (ver.120) |
Firefox (ver.120) | Safari (ver.17.1.2) |
---|---|---|---|
CE(か) | CE(か) | BI(か) | CE() |
BI(null) | CE(か) | ||
I(null) | I(か) | ||
BI(か) | |||
I(か) |
- Web ページ外の画面をクリック
Windows Edge (ver.120) Chrome (ver.120) |
MacOS Edge (ver.120) Chrome (ver.120) |
Firefox (ver.120) | Safari (ver.17.1.2) |
---|---|---|---|
CE(か) | CE(か) | BI(か) | BI(null) |
BI(null) | CE (か) | I(null) | |
I(null) | I(か) | BI(か) | |
I(か) | |||
CE() |
予測された単語の選択中に他のキーをクリック
以下は、k
キーを押し、IME 入力開始した後、さらに a
キーを押し、IME によってか
に変換された状態で画面をクリック
した場合の結果です。クリックした場所によって結果が変わる場合があります。
Windows Edge (ver.120) Chrome (ver.120) |
MacOS Edge (ver.120) Chrome (ver.120) |
Firefox (ver.120) | Safari (ver.17.1.2) |
---|---|---|---|
KD(Process) | KD (他の key) | KD(Process)【※1】 | BI(null) |
CU (下記) | CU (下記) | BI(下記) | I(null) |
BI(下記) | BI(下記) | CE(下記) | BI(下記) |
I(下記) | I(下記) | I(下記) | I(下記) |
CE(下記) | CE(下記) | (->CS()->) | CE(下記) |
(->CS()->) | (->CS()->) | (->CS()->) |
※1 クリックしてもkeydown
が発生させないキーがあります。
Firefox で 英数(Eisu)
キーやかな(KanjiMode)
キーを押しても keydown
は発生しません。
終わりに
最新のブラウザでは、Event の順番がブラウザ間で統一されつつあり、また、InputEvent の isComposing プロパティが主要なすべてのブラウザでサポートされ、IME 入力の制御が古いバージョンと比べて容易になっています。
ただし、IME 入力の確定の方法によって、IME 入力終了時に発生する Event の数や順序が異なる点は依然として残っており、IME 入力の制御が完全には容易になっていません。