日本語フォームの半角カナ・全角英数をinputとblurで使い分けて自動変換する話

日本語フォームの半角カナ・全角英数を input と blur で使い分けて自動変換する話 Web Development
この記事は約10分で読めます。

日本語フォームに混入する半角カナや全角英数を JavaScript で自動変換したい。そう思って、まず input イベントに手を伸ばしました。私もそうでした。ところが検証用の HTML を組んで実測すると、input ハンドラは想像と違う場面でしか動きません。先に答えを書きます。input イベントは IME 経由の入力では発火せず、実質コピペ専用でした。だから今回は、コピペは input で即時変換し、IME 経由を含む最終チェックは blur に任せる、という役割分担にしました。日本語フォーム実装シリーズの仕上げ(サジェスト前後編・フリガナ・IME Enter に続く5本目)です。

検証環境:macOS Tahoe 26.4.1 / Google Chrome 148 / ATOK 35.0.3(2026年5月)。Safari、Firefox、Windows + MS-IME、Google 日本語入力、iOS / Android は未確認です。サーバー側の正規化(WordPress なら mb_convert_kana 一発)には深く触れません。

そもそも、半角カナはどこから混入するのか

自分から進んで半角カナを打つ人は少ないです。でも混入経路は意外と多い。いちばん多いのがコピペで、銀行系の古い帳票や基幹システムは、いまも半角カナが標準の場面があります。そこから値をコピーして貼れば、半角カナのまま入る。Excel や CSV からのコピペでは全角英数(「2024」)も混じります。iOS / Android のソフトウェアキーボードも、機種や設定でカタカナを半角で出すことがあり、ユーザーは気づきません。残るは IME で意図的に出すパターンですが、ここが想像と違いました。ATOK 35 + Chrome 148 で全角英数「ABC」を出そうとすると、ATOK は最初から半角の「ABC」を返してくる。全角英数を出すには、半角で打ってから「全角に変換」というワンアクションが要ります。つまり、半角カナや全角英数の主な混入経路は、IME での意図的入力ではなく、コピペや別アプリからの流入でした。この事実が、後半の実装判断をほぼ決めます。

クライアントとサーバーのどちらで正規化するかは、両方で重ねるのが現実的です。サーバーの mb_convert_kana は確実(JavaScript は無効化されうるし bot には効かない)、クライアントは UX が良い(その場で正しい形に変わるのが見える)。クライアントで第一段階の変換をして UX を担保し、サーバーで念のためもう一度正規化する。保険を二重にかける感覚です。この記事はクライアント側に絞ります。

実測したら、input は IME を素通りしていた

変換を「いつ」走らせるかは、input(入力中)、compositionend(IME 確定直後)、blur(フォーカス離脱)、submit(送信時)の選択肢があります。検証用 HTML を組んで実測したところ、いちばんの驚きは input でした。ATOK で「あいうえお」を打って半角カナ「アイウエオ」に変換しても、value を書き換えるハンドラが一度も発火しません。

表示される input は、すべて isComposing=true です。ハンドラ先頭で if (e.isComposing) return; としているので、IME 経由では本体に到達しない。compositionend のあとに input が追加発火することもなく、value はそのまま。では input ハンドラはいつ動くのか。別アプリから「アイウ」をコピペすると、こうなりました。

isComposing=false の input が発火して、書き換えまで一気に動く。コピペした瞬間に半角カナが全角カナへ置き換わり、カーソル位置も維持される。ここで腑に落ちました。入力中ハンドラの本当の用途は、IME 経由の対応ではなく、コピペで流入した値の即時変換だったのです。コピペは文字数が変わらないことが多いので、setSelectionRange を呼ばなくてもカーソルは保たれます。なお、これは ATOK + Chrome の結果で、Windows + MS-IME や Google 日本語入力では IME 経由でも isComposing=false の input が追加発火する可能性があります。

IME経由は素通りし、コピペでだけ変換される=入力中ハンドラは実はコピペ専用、という図

結局、input(コピペ対策)と blur(IME 経由を含む最終チェック)の組み合わせを基本にしました。input は見た目が派手で「反応してる」安心感を与えますが、IME には届かない。blur はコピペでも IME 経由でも最終的に必ず通るので、保険として一番強い。submit 時の一括変換は確実ですが、ユーザーが結果を確認できないので、最後の保険に留めます。

NFKC は便利だが、①や㈱まで分解する

変換の中身は、String.prototype.normalize(‘NFKC’) がほぼ一発です。半角カナを全角カナに、全角英数を半角英数に、両方とも1行で変換できます。

ただ、これに乗ると地雷を踏みます。NFKC は「互換等価」の文字まで展開するからです。

NFKCは半角全角を直す反面、①→1や㈱→(株)など互換文字まで分解する両刃という図

丸付き数字、株式会社の合字、単位記号が全部分解される。ユーザーが「①対応希望」と入れてフォーカスを外した瞬間、「1対応希望」になります。文字数は同じでも、ユーザーから見れば打った文字と違う。だから、NFKC をかけるかはフィールドごとに決めるのが現実的です。姓名・カナ・電話・郵便番号のように文字種が絞られた欄にだけ NFKC を当て、自由記述や住所には触らない。副作用を確実に避けたいなら、半角カナと全角カナ、全角英数と半角英数の対応表だけを持つ自前テーブルを書く手もあります。コードは長くなりますが、それ以外の文字は触らずに済みます。手間と安全のトレードオフです。

カタカナとひらがなは、0x60 ずらすだけ

フリガナ欄で「全角カタカナのみ」にしたい場面があります。カタカナとひらがなは Unicode 上で 0x60(96)離れてきれいに並んでいるので、コードポイントを足し引きするだけで変換できます。

長音記号「ー」(U+30FC)は、ひらがなにもカタカナのレンジにも入っていません。共通で使う記号なので、変換対象から外しておけば問題ありません。「ヴ」(U+30F4)は範囲に含まれ、-0x60 で「ゔ」になるので、ここは気にしなくて大丈夫です。これで、ユーザーが「やまだ」「ヤマダ」のどちらを入れても、最終的に「ヤマダ」へ揃います。「Yamada」のようなローマ字は別の話なので、バリデーションで弾くか、ローマ字変換を別途仕込みます。

完成形のコードと、リリース前のチェック

ここまでの判断を入れた最小実装です。

input 先頭の if (e.isComposing) return; は、実測どおり IME 経由では isComposing=true の input しか来ないので、IME 中の動作に影響せず、コピペで isComposing=false の input が来たときだけ反応します。setSelectionRange は、文字数が変わる変換(半角濁点付きカナのコピペなど)への保険です。blur は最終チェック、submit は JavaScript が無効化された場合などの最後の保険で、サーバー側の正規化と二重に走っても害はありません。

WordPress のフォームプラグイン(Contact Form 7 など)でも、フィールドにクラス名を振って同じ JavaScript を読み込めば流用できます。条件付きフィールドや複数ステップで DOM が動的に増える場合は、MutationObserver で後付けのリスナー登録が要ります。リリース前は、半角カナの入力とコピペ、全角英数、ひらがな→カタカナ、自由記述で①が1にならないか、IME 変換中に値が勝手に書き換わらないか、そしてモバイル実機を確認しておきます。

もう一度、あの送信ログを思い出す

数年前、受託で作ったフォームの送信ログに「ヤマダ タロウ」のような半角カナが混じっていました。当時はサーバーの mb_convert_kana を1行通すだけで片付けて、画面には半角カナのまま残っていた。いま同じ案件をやるなら、クライアント側で先回りして変換する選択肢も出せます。シリーズを通して繰り返し効いたのは、入力規則をどこまで自動化して、どこからユーザーの責任にするか、という判断でした。フリガナ欄を編集可能のままにするのも、自由記述に NFKC をかけないのも、根は同じです。完璧な自動化を目指すと、必ずどこかで副作用が出る。そして検証してログを取ると、予想と違う場面が必ず出てきました。フリガナ実装ではひらがなフィルタが推測変換を意図せず救い、この記事では入力中ハンドラがコピペ専用だった。動かしてみないと分からない。これが、シリーズ5本で繰り返し効いてきた感覚です。

参考にした公式ドキュメント

関連記事

Web Development
この記事を書いた人
rapls

WordPressのプラグインを作っているフリーランスエンジニアです。Web開発はもう6年以上。WordPress.orgで Rapls AI Chatbot、Thanks Mail for Stripe、Rapls PDF Image Creator、Prime Cache の4本を公開し、保守を続けながら、日本語ロケールの翻訳エディター(PTE)も務めています。このブログに書くのは、現場で自分が実際にハマって、調べて、直した話です。

raplsをフォローする
raplsをフォローする

コメント

タイトルとURLをコピーしました