一般社団法人 全国個人事業主支援協会

COLUMN コラム

  • Mac環境でテキストボックス変換中に@keyup.enterで指定した関数が実行されちゃう事象の解決方法

Vue.jsにおいて、テキストボックス編集時にEnter押下したときの挙動は@keyup.enterで実行できます

.exactをつけて@keyup.enter.exactで指定されることが多いイメージ。

これが、Mac環境では非常に微妙な挙動をします。

Mac環境でテキストボックスに@keyup.enter.exactを指定すると、日本語変換を確定するためにEnterを押した際にも、イベントが発生してしまうんです。

今回、重い腰をあげてこの事象にちゃんと向かい合いました。

解決方法を2つ提示します。

isComposingで解決できない・・・?

セオリーでは、KeyboardEventのisComposingプロパティを監視して変換中かどうかを判定するようです。

詳細はMDNの以下のページを参照してください。

KeyboardEvent: isComposing プロパティ

 

Vue.jsでは@compositionstart、@compositionendのv-onディレクティブを使用して変換中かどうかを判定することができます。

 

これでいけるやん!と思って変数「変換中フラグ」を定義し、@keyup.enter.exactのタイミングでその変数を監視してみましたが、値がtrueになることはありませんでした・・・。

 

ちなみに、変換中の場合はEventから取得できるkeyCodeが229になるようですが、このやり方も使えません。かならず普通のEnterキーのkeyCode:13になります。

なぜこの事象が起きるのか?

原因は、ブラウザにおける イベントの発火順序 にあるようです。

Macの日本語入力環境で @keyup.enter を使用した場合、挙動は以下のようになります。

  1. Enterキーが押される(keydown

  2. IMEの変換が確定し、入力が完了する(compositionend

  3. ブラウザが「入力は終わった」と判断し、変換中フラグを降ろす

  4. Enterキーが離される(keyup) <- ここでイベントを検知

つまり、keyup イベントが走る頃には、ブラウザはすでに「今はもう変換中じゃないよ」という状態になっているため、通常のEnterキー押下と区別がつかなくなってしまうのです。

第1案:@keydown.enter.exact で実装する(推奨)

最もシンプルで、かつモダンなWebサービスの多くが採用している解決策です。

イベントを keyup ではなく keydown で拾うように変更します。

<template>
  <v-text-field
    v-model="message"
    @keydown.enter.exact="onEnter"
  ></v-text-field>
</template>

<script setup>
const onEnter = (event) => {
  // IME変換中のEnterであれば、event.isComposingがtrueになる
  if (event.isComposing) return

  // 確定後のEnterのみ実行
  submit()
}
</script>

なぜこれで解決するのか?

keydown イベントは、IMEの確定処理が走る 直前 に発火します。そのため、ブラウザが持っている isComposing(変換中フラグ)がまだ true の状態で判定ができるからです。

特別な理由がない限り、この「keydown + isComposing チェック」が最も無難な実装です。

世の中のサイトもkeydownで実装されているものが多いです。私の謎な思い込みでkeyupで実装していましたw

第2案:setTimeoutを使用してフラグ管理を遅延させる

「どうしてもキーを離したタイミング(keyup)で実行したい」という場合や、既存のロジックの都合で keydown が使えない場合の回避策です。

compositionend(変換終了)イベントが走った直後にフラグを下ろすのではなく、「イベントループの最後」までフラグ降ろしを待機させることで、keyup 側の判定を間に合わせるテクニックです。

 

<template>
  <v-text-field
    v-model="message"
    @keyup.enter.exact="onEnter"
    @compositionstart="isComposing = true"
    @compositionend="onCompositionEnd"
  ></v-text-field>
</template>

<script setup>
const isComposing = ref(false)

const onCompositionEnd = () => {
  setTimeout(() => {
    isComposing.value = false
  }, 100)
}

const onEnter = () => {
  if (isComposing.value) return

  submit()
}
</script>

なぜこれで解決するのか?

setTimeout を使うことで、フラグを更新する処理を「タスクキュー」の後方に回します。これにより、変換確定時に発生する keyup イベントが走り抜けるまで、isComposing = true の状態を擬似的に維持させることができます。

まぁ、setTimeoutである時点でお察しです。かなり無理やりなので最後の手段だと思います。

まとめ:どちらを使うべき?

  • 基本的には「第1案(keydown)」がおすすめ

    • コードがシンプル。

    • ブラウザ標準のプロパティ(isComposing)を活かせる。

    • ユーザー体験としてもレスポンスが速く感じる。

  • 「第2案(setTimeout)」を使う場面

    • keyup のタイミングでなければならない制約がある場合。

    • 古いブラウザ環境などで keydown 時の isComposing が不安定な場合。

Mac環境特有の「Enter暴発」に悩まされている方は、ぜひ試してみてください。

この記事をシェアする

  • Twitterでシェア
  • Facebookでシェア
  • LINEでシェア