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 が発生する。
keydownbeforeinputinputkeyup
- ただし、長押ししている間は
keyupが発生しない。keydownbeforeinputinput-
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 キーを押した際の結果です。
WindowsEdge (ver.120) Chrome (ver.120) |
MacOSEdge (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 キーをクリックした場合の結果です。
WindowsEdge (ver.120) Chrome (ver.120) |
MacOSEdge (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 キーをクリックした場合の結果です。
WindowsEdge (ver.120) Chrome (ver.120) |
MacOSEdge (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 キーをクリックした場合の結果です。
WindowsEdge (ver.120) Chrome (ver.120) |
MacOSEdge (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 ページ上の要素をクリック
WindowsEdge (ver.120) Chrome (ver.120) |
MacOSEdge (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 ページ外の画面をクリック
WindowsEdge (ver.120) Chrome (ver.120) |
MacOSEdge (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 によってかに変換された状態で画面をクリックした場合の結果です。クリックした場所によって結果が変わる場合があります。
WindowsEdge (ver.120) Chrome (ver.120) |
MacOSEdge (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 入力の制御が完全には容易になっていません。