LoginSignup
7
4

IME 入力 - ブラウザ間の違い

Posted at

IME 入力のブラウザ間での挙動の違いを調査したのでその結果をまとめます。

IME(Input Method Editor)
IME はテキスト入力をサポートするソフトウェアです。
かな入力時に(漢字などの)変換候補を表示する機能などを提供しています。

IME 入力は、日本語話者なら必ず使っているであろう機能であり、アジア圏のユーザを対象にしている Web アプリケーション内で、テキスト入力を制御したい場合は、IME 入力を制御する JavaScript コードを書く必要があるかもしれません。

JavaScript で IME 入力を制御する際には、以下のような点に注意が必要です。

  • ブラウザ間で、IME 入力に関係する Event の発生の順番が異なる場合があります。
  • 同じブラウザでもバージョンにより Event の発生の順番が異なる場合があります。
  • 同じブラウザで同じバージョンでも、入力エリアが textareainputcontenteditable かで 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
  • 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
  • Mozilla Firefox (mac)
    • macOS
      • version: 120.0.1
  • Safari (mac)
    • macOS
      • version: 16.1
      • version: 17.1.2

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

調査用コード

以下が調査に使用したコードです。

index.html
<!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 の要素は、QuillLexical などの WYSIWYG ライブラリの初期状態を参考に <div id="contenteditable" contenteditable="true"><p><br></p></div> としています。
  • addEvent 関数で以下のプロパティをコンソールに表示します。
    • InputEvent (beforeinput, input)
      • type: イベントの種別を表す文字列
      • data: 挿入された文字
      • isComposing: compositionstartcompositionend の間に発生したかを示す値
    • KeyboardEvent (keydown, keyup)
      • type: イベントの種別を表す文字列
      • key: ユーザーが押したキーの値
      • isComposing: compositionstartcompositionend の間に発生したかを示す値
    • CompositionEvent (compositionstart, compositionupdate, compositionend)
      • type: イベントの種別を表す文字列
      • data: イベントを発生させた入力システムによって生成された文字

調査結果

要点

すでに文章が長くなり始めているので先に重要な点を簡単にまとめます。

  1. 最新のブラウザでは、KeyboardEvent を除くと、compositionupdate -> beforeinput -> input の順で Event が発生している。
  2. IME 終了時の Event の発生の順番は、多くパターンがあり、InputEvent や KeyboardEvent が発生せずに compositionend しか発生しないパターンもある。
  3. KeyboardEvent は IME 入力(を含むテキスト入力処理)の制御に向いていない。

    テキストの入力を処理したい場合は、代わりに input イベントを使用してください。ユーザーが他の種類のテキスト入力、例えば、タブレット端末やタブレット機器による手書き入力システムなどを使用している場合、キーボードイベントが発生することはありません。
    KeyboardEvent - Web API | MDN

  4. input のプロパティの値は、contenteditable 要素では扱いづらい。
  5. バージョン 16.4 以降の Safari では inputEvent.isComposing がサポートされており、IME 入力中かの判断が容易になった。
  6. Microsoft Edge は バージョン 79 から Google Chrome と同じ Chromium ベースに移行しため、Microsoft EdgeGoogle Chrome は同じような挙動をする。

非 IME (ローマ字入力)

IME 入力の Event を調査する前に、まずローマ字入力に焦点を当てます。

  • ローマ字入力ではブラウザ間やバージョン間で差異がなく、以下の順番で Event が発生する。
    1. keydown
    2. beforeinput
    3. input
    4. keyup
  • ただし、長押ししている間はkeyup が発生しない。
    1. keydown
    2. beforeinput
    3. input
    4. keydown: 長押ししている間、keydown が発生し続ける。
    5. keyup: キーが離されるとはじめて発生する。
  • 以下の場合、input が発生しない。
    • 削除する対象がないのに、BackspaceDeleteControl + D
    • input タグ内で Enter もしくは Shift + Enter
  • 以下の場合、Safari は beforeinput も発生しない(Chrome と Edge、Firefox は発生する)
    • 削除する対象がないのに、BackspaceDeleteControl + D
    • input タグ内で Enter もしくは Shift + Enter
  • isComposingInputEventKeyboardEvent いずれも false
  • InputEventdata は入力値(基本的に beforeinputinput で同じ)
  • BackspaceBackspace + fnControl + DEnterShift + Enter の場合は、InputEventdata の値はどれも null

InputEventinputType プロパティを使用することで、nullの識別ができます。

以下は、inputType の値の、input タグ、textarea ダグ、contenteditable 要素での違いをまとめたものです。
BackspaceDeleteControl + D は削除する対象があることを前提としています。(削除する対象がない場合は上記で示したように Event 自体が発生しないことがあります。)
※ 特に分けて書いていない限り、beforeinputinputinputType の値は同じです。表内では、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 になる。)

ローマ字入力まとめ

以上のように、ローマ字入力に関しても、ブラウザ間での違いが見受けられます。
テキスト入力時はブラウザ間でイベントの発生順に違いはないですが、EnterShift + Enterのキー入力時やテキスト削除時では、イベントが発生しなかったり、発生した場合でもプロパティの値が異なることがあります。

ローマ字入力の制御をクロスブラウザ対応する場合、input タグtextarea ダグでは input イベントと beforeinput イベントの両方が使えそうですが、contenteditable 内では input イベントの inputType は信用ができず、beforeinputイベントで制御するのが適切なようです(Safari では contenteditable 内で EnterShift + 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 つのフェーズに分けて説明します。

  1. IME 入力開始時
  2. IME 入力中
  3. 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 の新旧バージョンでそれぞれ beforeinputcompositionupdate の発生の順番が入れ替わっています。
  • 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 の新旧バージョンでそれぞれ beforeinputcompositionupdate の発生の順番が入れ替わっています。
    • Mac の Edge と Chrome は keyup は 1 回しか呼ばれません。data プロパティの値は入力したキーです。
  • Firefox
    • Edge や Chrome の新バージョンと同じ順序で Event が発生しますが、keydown イベントでは、data プロパティの値はProcessです。
    • compositionupdate の data プロパティの値は空文字でなく、<empty string>です。
  • Safari
    • KeyboardEvent を除けば、Edge や Chrome の新バージョン、Firefox と同じ順序で Event が発生しますが、keydowninput の後に回されています。
    • バージョンが 16.4 よりも前の Safari の場合InputEvent の isComposing プロパティの値は undefined になります。

IME 入力中

IME 入力中つまり「IME 入力開始後で、IME 入力が確定するまでの間」の Event の発生の順番を見ていきます。

具体的には以下の 3 つのケースが想定されます。

  1. 次の文字が入力される(例: あ -> あい)
  2. 予測された単語の選択(例: あい -> 愛)
  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 は beforeinputcompositionupdate の発生の順番が入れ替わっているだけなので表にはまとめていません。
  • バージョンが 16.4 よりも前の Safari の場合は InputEvent の isComposing プロパティの値は undefined になります。
  • KeyboardEvent を除けば、すべてのブラウザで compositionupdate -> beforeinput -> input の順番で Event が発生します。

IME 終了時

IME 終了時つまり「IME 入力が確定する時」の Event の発生の順番を見ていきます。

具体的には以下の 4 つのケースが想定されます。

  1. Enter キーをクリックして IME 入力を確定
  2. IME 入力中に入力中の文字を全て削除(例: あ -> )
  3. IME 入力中に画面をクリック
  4. 予測された単語の選択中に他のキーをクリック

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 入力中の 文字を全て消す
    -> inputdata プロパティの値は nullになる。
  • 2 つのキー以上(例: か, 12) をクリック後、IME 入力中の 文字を全て消す
    -> inputdata プロパティの値は削除直前の文字になる。

これは、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 入力中の 文字を全て消す
    -> inputdata プロパティの値は nullになる。
  • 2 つのキー以上(例: か, 12) をクリック後、IME 入力中の 文字を全て消す
    -> inputdata プロパティの値は削除直前の文字になる。

これは、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 入力の制御が完全には容易になっていません。

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4