IME が有効だと文字入力をキャンセルできない
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ キャンセルできず compositionstart イベントも起きる
◆ compositionstart イベントもキャンセルできないので IME の変換中状態を防げない
◆ 変換中の状態で input の値を更新すると IME 内部状態とズレが出て入力がおかしくなる
◆ compositionstart イベントもキャンセルできないので IME の変換中状態を防げない
◆ 変換中の状態で input の値を更新すると IME 内部状態とズレが出て入力がおかしくなる
入力をキャンセルできない
<!doctype html>
<input id="input">
<script>
input.addEventListener("keydown", event => {
event.preventDefault()
})
</script>
これで キー入力をキャンセルしてるので何も入力されないはずと思っていたのですが IME が有効だと普通に入力されます
仕様的にはキャンセルできるようなんですけどね
https://w3c.github.io/uievents/#events-composition-canceling
preventDefault でキャンセルされたら IME の変換状態の開始の compositionstart イベントも起きないらしいのですが compositionstart イベントが起きてます
この compositionstart イベント自体もキャンセルできるらしいのですが キャンセルしても IME の変換中状態に入ってるので この辺の機能は未実装みたいです
自力で入力前に戻す
入力自体は防げないので できる対処は入力後にもとの値に戻すくらいでしょうかIME が有効のときの入力のみキャンセルするならこういう感じです
<!doctype html>
<input id="input">
<script>
let prev = ""
input.addEventListener("input", event => {
if (event.inputType === "insertCompositionText") {
input.value = prev
} else {
prev = input.value
}
})
</script>
keydown だとまだ input.value が更新されてないので input イベントにしています
IME が有効な状態で入力されると inputType が insertCompositionText になります
この場合に前回の value に戻してます
一応は達成できてますが これでも問題はあって IME の内部的には変換処理の途中という扱いのようです
例えば「てすと」と打って変換しようとします
入力前の状態に戻しているので input 自体は空文字です
ですが 「てすと」 と打ったかのように IME の変換候補画面は出てきて候補を選択できます
確定してもその入力がキャンセルされるので意味がない物が出てくるという状態です
compositionstart がキャンセルできればこの状態は防げそうですが できないので仕方ありません
一応 一度フォーカスを外してから再フォーカスするとか readonly にしてから外すとか試しましたが IME の変換中状態は継続されていました
変換すると問題に
入力を無効にするのなら IME の候補が出てきて邪魔というくらいでしたが入力値を変換しようとすると問題が起きます単純に入力ごとに末尾に - を追加してみます
<!doctype html>
<input id="input">
<script>
input.addEventListener("input", event => {
input.value += "-"
})
</script>
これで「てすと」と入力すると
t-て-て-て-て-てすと-
「tesuto」 と打って 「te」 が「て」 に変わったり少し複雑なので変換がない 「123」 を試してみます
1-1-123-
これでも期待する 「1-2-3-」 にはなりません
途中の状態を見ると
1-
1-12-
1-1-123-
のようになっています
原因
IME が変換途中の状態で画面を更新するときには 変換途中のものを一旦消してから新しい状態を入力するという動きになっています今回の例で 「2」 を入力するときには IME 的には 「1」 が入力途中なので 1 文字消してから 「12」 を入力するという動きです
「1-」 から 1 文字消して 「1」 となって ここで一度 input イベントが起きるので 「-」 が追加されて 「1-」 です
ここに 「12」 を追加するので 「1-12」 となって またここで input イベントが起きて 「-」 が追加されて 「1-12-」 の完成です
さらに 「3」 を入力すると IME 的には 「12」 の 2 文字が入力途中なので 2 文字消して 「123」 の追加です
「1-12-」 から 2 文字消して 「1-1」 になって 「-」 追加して 「1-1-」
ここに 「123」 を追加して 「1-1-123」 になって 「-」 を追加して 「1-1-123-」 となるわけです
対処
消されたときと追加されたときはどっちも input イベントですが inputType が違います消されたときは deleteContentBackward で追加されたときが insertCompositionText です
ただ deleteContentBackward は普通にバックスペースで消したときにも発生するので IME が内部的に行ったものかの判断は難しいです
文字数が変わらない大文字小文字の変換などなら問題なさそうですが 文字数が変わると IME の内部状態のズレが問題になってきます
こういう問題があるので IME の変換途中をうまく扱おうとするよりは compositionstart イベントから compositionend イベントまでは input イベントを無視して 変換完了後に一度にコピペで貼り付けられたかのように扱うので良いかもですね
compositionstart から compositionend は自分で管理しなくても input イベントの isComposing プロパティを見ればよいです
<!doctype html>
<input id="input">
<span id="output"></span>
<script>
input.addEventListener("input", event => {
if (!event.isComposing) output.textContent = input.value
})
input.addEventListener("compositionend", event => {
output.textContent = input.value
})
</script>
試したブラウザと IME は Chrome + Google 日本語入力 です
他の組み合わせだと違うところもあるかもしれません