新しいものに書き直しました
下記のものではうまくイベントが発火しない場合がありましたので、inputやtextareaの値が変わったらイベント発火(IME入力中を除く)に新バージョンとして書き直しましたので、そちらも参照ください。
はじめに
テキスト入力項目で英数字は半角に統一したいけど、ユーザに「英数字は半角で入力してください」なんてエラーを表示するのはいただけないよねと思い、「それなら自動でフィルタリングしてあげればいいじゃん」なんて考えてしまったのが運のつき
日本人には切っても切れないIME入力という魔物が住んでいました
IMEの入力でなければ「keyup」のイベントを拾って変換してやればよいのですが、IME入力中にこれをやっちゃうと入力している文字が確定されちゃう
ならば「keyup」のイベントをディレイしてやる と思ったら、キー入力の遅い人だと途中でやっぱり確定されちゃう
はいはい手抜きしようと思った私が悪かったです、ちゃんとIME入力確定を見極めてやる と思ったらIME様は行く先のブラウザごとに違う動作をしやがる
キーーーーーーッ
ということで分かる範囲でIMEの入力時の挙動を調べてみました。
そしてフィルタリングの部分は人それぞれだと思ったので、イベントを発行することで汎用的に使えるものをIME様の動作に翻弄されながらなんとかjQueryのプラグインとして作ってみましたよ
ソースは「GitHub」に置いています。
ついでにカット・ペースト時にもイベントが発火するように仕込んでいます。
説明とデモは「こちらのページ」を見てください。
Windows7でしか確認していないのでベータ版みたいなものですが、GitHubのissueに「動作報告」や「バグ・改善の報告」をいただけると助かります。
というかWindows7しか持っていないのでプルリクあげてもらえると大いに助かります。
ブラウザごとのIME入力時の挙動
Internet Explorer11
珍しく一番素直な動作をしている
- 「keydown」時にキーコードが「229」の時はIME入力中
- 入力の確定時に「keyup」でキーコード「13」が渡される
- 文字入力中も「keyup」が発火する
以上の特徴から「keydown」でキーコードが「229」の後に「keyup」でキーコード「13」がくれば入力確定となる
- 「keydown」時にキーコードが「229」が渡されたときはIME入力中の変数にtrueをセット
- 「keyup」時にキーコード「13」が渡され、IME入力中の変数の値がtrueの時はIME入力確定
Chrome
一番厄介と感じているのは意外にもこいつ
- 「keydown」時にキーコードが「229」の時はIME入力中
- 入力の確定時には「keyup」は発火しない
- 通常の文字入力時は「keyup」は発火している
以上の特徴から「keydown」時にキーコードが「229」の時は一定時間後「keyup」が無ければ入力確定と考えられるので
- 前回「keydown」時に設定したタイマー処理のクリア
- 「keydown」時にキーコードが「229」の時に
300500ミリ秒後にキーアップしたかどうか確認するタイマー処理をセット(タイマー処理内でキーアップが確認できないときは入力確定として強制的にキーコード「13(エンター)」の「keyup」イベントを発火) - 一旦キーアップ確認用の変数をfalseにセット
- 「keyup」時にキーアップ確認用の変数をtrueにセット
以上でIMEの入力確定を捕捉することができる。
※タイマーのタイミングなのかキーを押しっぱなしにした時は最初の一文字目の時にIME入力確定のイベントが発火されてしまう。(タイマーの時間については今後調整必要)
キー入力の速い人や、押しっぱなし時の対策として500ミリ秒に設定しなおしました。
Firefox
可とも不可ともいえない動作
- IME入力の一番最初に「keydown」でキーコード「0」が渡されるとIME入力モードに突入
- エンターキーを押すまではキーイベント一切無し
- 入力の確定時に「keyup」でキーコード「13」が渡される
以上の特徴から「keydown」でキーコードが「0」の後に「keyup」でキーコード「13」がくれば入力確定となる
- 「keydown」時にキーコードが「0」が渡されたときはIME入力中の変数にtrueをセット
- 「keyup」時にキーコード「13」が渡され、IME入力中の変数の値がtrueの時はIME入力確定
IME入力中にキーイベントがまったく起きないという実装もどうかと思うが...
ソースの一部抜粋
2015/03/03内容更新
var _this = this;
// タイマー用
var timer;
// IME入力中かどうかを判断するフラグ
var imeFlag = false;
// キーアップイベントが発火したかどうかを判断するフラグ
var keyUpFlag = true;
// 直前のキーアップコード
var beforeKey = 0;
// 捕捉するキーイベント
var keyEvents = [
'keydown.' + pluginName,
'keypress.' + pluginName,
'keyup.' + pluginName
];
// カット・ペーストのイベント
var otherEvents = [
'cut.' + pluginName,
'paste.' + pluginName
];
// 一旦イベントのoff
this.off();
// カット・ペースト時
_this.$elm.on(otherEvents.join(' '), function (event) {
var $elm = $(this);
// 右クリックでのカット・ペーストのイベント対策
setTimeout(function () {
// enter.imeEnterイベントの発行
_this._trigger($elm);
}, 0);
});
// キーイベント
_this.$elm.on(keyEvents.join(' '), function (event) {
var $elm = $(this);
// イベント発火時のキーコード
var keyCode = event.keyCode;
// イベントを実行しないKeyupキーコード
var exceptKeys = [
3, // Cancel
6, // Help
9, // TAB
15, // Command
16, // Shift
17, // Ctrl
18, // Alt
19, // Pause
20, // Caps Lock
21, // KANA
22, // Mac"英数" キー
23, // JUNJA
24, // FINAL
25, // KANJI
27, // Esc
28, // 「前候補・変換」
29, // 「無変換」
30, // ACCEPT
31, // MODECHANGE
33, // Page Up
34, // Page Down
35, // End
36, // Home
37, 38, 39, 40, // 矢印
41, // SELECT
42, // PRINT
43, // EXECUTE
44, // Print Screen
45, // Ins
91, // 左Windowsキー
92, // 右Windowsキー
93, // 右クリックキー
95, // SLEEP
112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, // ファンクションキー
124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, // ファンクションキー
144, // Num Lock
145, // Scroll Lock
224, // Command
225, // AltGr
240, 241, // Caps Lock
242, // 「カタカナ・ひらがな」
243, 244 // 「半角・全角」
];
// イベントのタイプごとに処理
// 通常は「keydown」→「keypress」→「keyup」の順にイベントが発火する
switch (event.type) {
case 'keydown':
// 一旦IME入力中フラグのリセット
imeFlag = false;
// 設定していたタイマーのリセット
clearTimeout(timer);
// キーコードが229の場合はIME入力中とみなす(Chrome・IE用)
// キーコードが0の場合はIME入力開始とみなす(FF用)
if (keyCode === 229 || keyCode === 0) {
// IME入力中フラグのセット
imeFlag = true;
}
// Chrome対策
// キーコードが229の場合
// 直前のキーアップコードが「半角・全角」でない
// 直前のキーアップコードが「カタカナ・ひらがな」でない
// 直前のキーアップコードが「Caps Lock」でない
// alt, ctrl, option, commandキーの同時押しでない
if (keyCode === 229 && beforeKey !== 240 &&
beforeKey !== 241 && beforeKey !== 242 &&
beforeKey !== 243 && beforeKey !== 244 &&
!event.altKey && !event.metaKey && !event.ctrlKey) {
// キー入力から500ミリ秒後にkeyupイベントの
// 無い場合は入力確定とする
timer = setTimeout(function () {
if (!keyUpFlag) {
// enter.imeEnterイベントの発行
_this._trigger($elm);
}
}, 500);
// キーアップフラグのリセット
keyUpFlag = false;
}
break;
case 'keypress':
// IME入力中はキープレスイベントは発火しないので
// このイベントが発火した場合はIME入力中以外とみなす
imeFlag = false;
break;
case 'keyup':
// キーアップフラグのセット
keyUpFlag = true;
// 直前のキーコード確認用
beforeKey = keyCode;
// 文字入力以外のキー入力は除外
// alt, ctrl, option, commandキーの同時押しの場合は除外
if (exceptKeys.indexOf(keyCode) !== -1 ||
event.altKey || event.metaKey || event.ctrlKey) {
return;
}
// IME入力中でない
// IME入力中でキーコード13が発行された
if (!imeFlag || (imeFlag && keyCode === 13)) {
// enter.imeEnterイベントの発行
_this._trigger($elm);
}
break;
}
});
まとめ
まとめというほどでもありませんが、ブラウザごとにIME入力時の挙動が異なるためとても悩みました...
IMEの入力確認だとテストコードも書けないし...
Chromeが一番厄介に感じましたが、レンダリングエンジンがIMEに関係しているとすると、もしかするとSafariも同じような実装なのかもしれないですね。
どうやらSafariはIEと同じ動作のようです。
こちら参照freefielder.jp
現状でタイマーを使用して確認していますが、マシンパワーによってはタイミングが異なるかもしれません。私の環境では300ミリ秒がベストかなと感じました。(これ以上短くするとkeyupが発火していないと判断されるときが多くなる)
キー入力の早い人や、キー押しっぱなしの時は「keyup」イベントがスキップされてしまうので500ミリ秒にしてみました。もっさりした動作になっちゃうのですが仕方が無い...
この辺の挙動は何とかしてほしいところですね 全く
キーコードの確認には下記のサイトを利用させていただきました。