入力フォームがあって、Enterを押して入力したテキストをリストに追加したいとしましょう。↓こちらでお試しできます。
- ここに追加されるよ
こういう感じで実装できます。簡単ですね。
(() => { const list = document.querySelector("#list_1"); const input = document.querySelector("#input_1"); input.addEventListener("keydown", (e) => { if (e.key !== "Enter") { return; } e.preventDefault(); const text = e.target.value; const li = document.createElement("li"); li.textContent = text; list.appendChild(li); e.target.value = ""; }); })();
ところでこの実装には問題があって、IMEの変換をEnterで確定すると、環境によっては確定直前? 直後? の文字がそのまま追加されてしまうことがあります。
以下はMacのGoogle Chrome 91での動作例です。AquaSKKの確定直前の文字 ▼犬
が追加されてしまっています。これはSKKに限らず、たとえばMacデフォルトのIMEでも同じ問題が発生します。困りましたね。
IMEの変換中はEnterを押してもリストに追加されてほしくないですね。いいプロパティがないか探してみると KeyboardEvent.isComposing
というものがあることに気づきます。
IMEによる変換中は真を、そうでない場合は偽を返すようです。これは使えそうですね。そうして改良した入力フォームが↓こちらです。
- ここに追加されるよ
(() => { const list = document.querySelector("#list_2"); const input = document.querySelector("#input_2"); input.addEventListener("keydown", (e) => { if (e.key !== "Enter" || e.isComposing) { return; } e.preventDefault(); const text = e.target.value; const li = document.createElement("li"); li.textContent = text; list.appendChild(li); e.target.value = ""; }); })();
これでMacのChromeでも ▼犬
が入力されずに済みました。よかったですね。
ところで、改良した実装でもまだ問題になるケースがあります。Mac Safariで 犬と猫
という文字を追加してみましょう……。
おや、まだ 犬
しか入力していないのにリストに追加されてしまいました。どうやらSafariでは keydown
イベントの isComposing
プロパティを見る方法でもうまくいかないようです。
2019年の情報ですが、以下の記事が参考になります。Safariでは「Enter押下で確定」時の keydown
イベントの isComposing
が真にならないようでした。
なんとかできないか、とちょっと考えた末に以下の作戦を考えました。
compositionstart
イベントとcompositionend
イベントを自前で監視して、compositionend
イベントが発火した直後かどうかを判定できるようにする- Safariでは
↑の判定をcompositionend
イベントの発火直後のkeydown
イベントを間引くKeyboardEvent.isComposing
の代わりに使う- 2021/6/30: 「
keydown
イベントを間引く」よりも「自前でcompositionend
イベントを監視して判定する」の方が表現として適切そうだったので修正しました
- 2021/6/30: 「
これを試したのが↓の入力フォームです。いかがでしたか?
- ここに追加されるよ
(() => { const list = document.querySelector("#list_3"); const input = document.querySelector("#input_3"); // SafariっぽいUAのとき、compositionend イベントの直後かどうか判定できるようにする const isSafari = navigator.userAgent.includes("Safari/") && navigator.userAgent.includes("Version/"); let isCompositionFinished = true; input.addEventListener("keydown", (e) => { if (isSafari && isCompositionFinished) { isCompositionFinished = false; return; } if (e.key !== "Enter" || e.isComposing) { return; } e.preventDefault(); const text = e.target.value; const li = document.createElement("li"); li.textContent = text; list.appendChild(li); e.target.value = ""; }); input.addEventListener("compositionstart", () => { isCompositionFinished = false; }); input.addEventListener("compositionend", () => { isCompositionFinished = true; }); })();
こういうことをしなくても済むようになりたいですね。もっといい方法があれば教えてください。