HTMLで数値入力を全角・半角意識せずにリアルタイムで入力できるようにしようとしたけど失敗だった件
2025-06-03追記
このサンプルだとコピペや数値の途中行に出来ないので、もうちょっとカスタマイズが必要そう。
カスタマイズを色々するくらいなら、素直に onBlur にするのが良さそうではある。
発端
htmlの入力フォームで <input type="number">
タグを使っていたが、全角入力モードだと入力ができないというお問い合わせが来た。
全角でも入力できるように、<input type="text">
タグにして、onChangeイベント時に半角に変換するJavaScriptコードを入れたが、WindowsのChromeだと入力がおかしくなった。
調査の結果、IMEの挙動でWindowsのChromeだとonChangeイベント時に取れる値がMacOSと違うことが判明。
さらに調査してみると上記挙動には onChangeイベントではなく、onBlurイベントを使うのが推奨されているとのこと。
だが、それだとリアルタイムに半角に変換(全角入力を確定しない状態で変換)はできないからなんとかならないか?というのが事のあらすじ。
(個人的には全角で入力が終わった後のエンターキー押下後に変換してくれるぐらいで良くないかな?とは思う)
実装結果
リアルタイム変換は譲れないという話が出て、職場のエンジニアがAIに聞いて出したコードが面白かったので、少し自分なりにアレンジしたかつ、コメントを追記したものが下記となる。
Reactのコードとなるが、React部分はかなり簡単なコードなので、下記でコピペして動かしてみてほしい。
import { useState } from 'react';
export function App(props) {
const [text, setText] = useState('');
const handleKeyDown = (event) => {
console.log("event info:", event.key, event.code, event.keyCode);
if (!isNumberKey(event)) return
const keyValue = extractNumberValue(event);
if (keyValue !== null) {
console.log("数字キー押下:", keyValue);
setText(text + keyValue);
}
};
return (
<div>
<h2>全角でも半角でもリアルタイムに数値入力するサンプル</h2>
<input
type="text"
value={text}
onChange={()=>{}} // deaultValueを指定しないとonChangeが必要という警告が出るため空関数を設定
onKeyDown={handleKeyDown}
/>
</div>
);
}
// Log to console
console.log('Hello console')
/**
* NumberKeyの押下判定
*/
const isNumberKey = (event) => {
if (event.ctrlKey || event.altKey || event.metaKey) return false;
// 複数の判定方法を組み合わせ
// event.keyだけだと全角入力時にevent.key==='Process'という
// 変換中を表すコードが入ってくるので回避するために下記のようになるっぽい
// keyCodeが非推奨なので最新ブラウザのみ対応でよいならkeyCode部分は消してよい
const keyChecks = [
() => /^[0-9]$/.test(event.key),
() => /^Digit[0-9]$/.test(event.code),
() => /^Numpad[0-9]$/.test(event.code),
() => event.keyCode >= 48 && event.keyCode <= 57, // 上段数字
() => event.keyCode >= 96 && event.keyCode <= 105 // テンキー
];
return keyChecks.some(check => {
try { return check(); } catch { return false; }
});
}
/**
* eventオブジェクトから半角の数値文字列を取得する
* event.key以外だと抽出が必要なため、下記のようなコードとなる
*/
const extractNumberValue = (event) => {
if (/^[0-9]$/.test(event.key)) return event.key;
if (/^Digit([0-9])$/.test(event.code)) return event.code.slice(-1);
if (/^Numpad([0-9])$/.test(event.code)) return event.code.slice(-1);
if (event.keyCode >= 48 && event.keyCode <= 57) return String(event.keyCode - 48);
if (event.keyCode >= 96 && event.keyCode <= 105) return String(event.keyCode - 96);
return null;
};
問題点
コピペとかカーソル移動しての数値入力とかブラウザがやってくれているところを自前実装する必要があり問題だったなぁという所感。
全角入力時にリアルタイムに半角に変換したいだけにしては、超えるハードルが高すぎる気がする。
というか、inputタイプがnumberのときは全角数値でも半角に変換するというのをブラウザ側でやってくれればいいのにと思う。
全角入力時に何も入力されないから問題だと思われる。
ひとこと
複雑ですが、1回導入しちゃえば楽に入力できるのは事実かなと。
数値入力だけなのに深いですね。
Discussion