JavaScriptで日本語入力の文字数制限を行いたい
テキストエリアの入力文字数制限をしたいとき、change eventを拾って入力値をチェックするだけだと、変換を伴う文字入力の際に期待した動きにならなった。
環境
jsなら何でもいいんですが、今回私はreactを使ったのでreact versionを記載します。
Service | Version |
---|---|
react | 18.2.0 |
上手く動かなかったコード
import React, { memo, useCallback, useState, type ReactElement } from 'react'; const TEXT_LEN = 5; function Hoge(): ReactElement { const [text, setText] = useState<string>(''); const handleChangeText = useCallback((e: React.ChangeEvent<HTMLInputElement>): void => { const text = e.target.value; setText((prevText: string): string => text.substring(0, TEXT_LEN)); }, []); return ( <input name="text" type="text" value={text} onChange={handleChangeText} /> ); }; export default memo(Hoge);
環境によって挙動が違う。 他のOSやブラウザ、バージョンによっても違うかもしれないが、いずれにしても思った挙動にならないので調査は以下のみ。
OS | Version | ブラウザ | 挙動 |
---|---|---|---|
Windows | 10 | Chrome | テキスト編集システムを使って指定文字数以上を入力すると、既に入力されている文字を消しながら入力される。 |
Mac | ventura | 13.2.1 | 入力可能文字数内のひらがなのみ入力され、変換を確定しても無視される。 |
iOS | 16.3.1 | Safari | 入力可能文字数内のひらがなのみ入力され、変換を確定しても無視される。 |
Android | 12 | Chrome | 変換途中でも文字数が肥えた時点で文字入力が確定され、入力できない。 |
原因
IME等のようなテキスト編集システムが原因。
対応
composition{start|update|end}
でテキスト編集システムの編集セッションイベントが取れる。
これを使えばどうにかできるのではと考え、onCompositionEnd
で入力を確定させる対応を行ったが、onChange
での文字入力も同時に行わないと思った通りの挙動にならなかった。
最終的には以下の形に落ち着いた。
import React, { memo, useCallback, useRef, useState, type ReactElement } from 'react'; const TEXT_LEN = 5; function Hoge(): ReactElement { const [text, setText] = useState<string>(''); const isCompositionStart = useRef<boolean>(false); const commitStr = useCallback(() => { setText((prevText: string): string => prevText.substring(0, TEXT_LEN)); }, []); const handleCompositionStart = useCallback((): void => { isCompositionStart.current = true; }, []); const handleCompositionEnd = useCallback((): void => { isCompositionStart.current = false; commitStr(); }, []); const handleChangeText = useCallback((e: React.ChangeEvent<HTMLInputElement>): void => { setText(e.target.value); if (!isCompositionStart.current) { commitStr(); } }, [isCompositionStart.current]); return ( <input name="text" type="text" value={text} onCompositionStart={handleCompositionStart} onCompositionEnd={handleCompositionEnd} onChange={handleChangeText} /> ); }; export default memo(Hoge);
文字入力自体はいかなる状態でも受け入れる。 この対応をしないと変換前の文字で確定したり、意図した挙動とならない。
しかし文字数制限は行いたいので、文字入力後にcommitStr
で文字数制限を行う。
ただしテキスト変換システムを使っている場合は、変換確定前に文字数を削ると変換がキャンセルされたりと意図しない挙動となる。
そこでonCompositionStart
でテキスト変換中であるかを確認し、もし変換中であれば文字数確定をスキップする。
しかしこのままではテキスト変換システムを使った入力ではテキスト文字数が制限されない。
そのためisCompositionEnd
で文字数確定を実施する。
また、変換が完了しているため、テキスト変換中フラグを下げる。
所感
文字数制限について調べると、maxlength
を使ったりjsを使ったりという手法は色々出てくるが、IME等での変換が上手くいかない件に関して無視されがちだったので、備忘録をかね残しておくこととする。
もうちょいいい感じにできそうな気がするが、まあ動くしもういいかな。