許多即時通訊介面會在使用者按下 <textarea> 中的 Enter 時提交訊息。
這適用於使用拉丁鍵盤直接輸入文字 (例如英文) 的使用者,但如果使用者使用輸入法編輯器 (IME) 輸入日文、中文或韓文等語言,則會中斷。
在這些情況下,Enter/Return 鍵用於確認目前的字元轉換候選字。如果自訂 JavaScript 監聽 Enter 上的 keydown 並立即提交,系統會在使用者仍在轉換字元時傳送訊息,導致訊息不完整、片段或不正確。
組合啟用檢查適用於任何作用於 Enter 的自訂 keydown 處理常式,不限於多行 <textarea> 欄位。單行 <input> 欄位有自己的 Enter 處理方式,例如搜尋和尋找列、內嵌重新命名欄位、互動視窗對話方塊欄位和指令面板,會收到與 <textarea> 相同的 IME 確認 Enter 按鍵按下事件,且 isComposing 設為 true。如果 <input> 沒有自訂 Enter 處理常式,則原生隱含提交 <form> 時,不需要明確處理,因為當 Enter 按鍵動作由輸入法編輯器消耗時,瀏覽器會抑制隱含提交。
導入策略
如果 <textarea> 或 <input> 具有自訂的「按下 Enter 鍵提交」功能,請先檢查 KeyboardEvent 的原生 isComposing 屬性,再提交內容。此外,請呼叫 event.preventDefault():Enter 的預設動作會在 <textarea> 中插入新行,並在 <form> 內的 <input> 中觸發瀏覽器的隱含提交,這會提交第二次。
<form id="chat-form">
<label for="chat-input" class="visually-hidden">Message</label>
<textarea id="chat-input" placeholder="Type a message..."></textarea>
<button type="submit" id="send-button">Send</button>
</form>
const textarea = document.getElementById('chat-input');
const form = document.getElementById('chat-form');
textarea.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
// Prevent the default action (newline in a textarea, implicit form submission in an input)
event.preventDefault();
// If the user is composing text, return early.
if (event.isComposing) {
return;
}
form.requestSubmit();
}
});
如果是單行 <input>,請使用不含 !event.shiftKey 條件的相同處理常式,因為 Shift+Enter 會在 <textarea> 中插入換行符號。
無障礙功能和測試
- 明確的提交按鈕:一律加入
<button type="submit">元素。鍵盤快速鍵是輔助工具,不得取代原生表單提交路徑。 - 無障礙輸入:確保所有
<input>和<textarea>元素都透過程式輔助方式,使用相符的id和for屬性與<label>建立關聯。
備用策略
在 Safari 的近期版本中,事件排序問題 (WebKit 錯誤 165004) 會在確認 Enter keydown 之前,將 compositionend 傳送至指令碼處理常式,即使基礎事件是以相反順序調度也一樣。keydown 處理常式執行時,event.isComposing 已重設為 false,這表示單靠標準檢查無法防止過早提交。
如需支援舊版 Safari 和其他平台的跨瀏覽器相容性,請採用下列其中一種備用策略:
策略 1:新增 keyCode === 229 檢查 (建議)
將 event.isComposing 與 event.keyCode === 229 檢查配對,即可可靠地擷取 Safari 的不按順序確認 Enter 按鍵。由於這項功能位於 event.key === 'Enter' 閘道下方,因此不會受到可能使用 229 進行一般字元版面配置輸入的行動虛擬鍵盤影響。
textarea.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
// Block submission if composing natively or if keyCode is 229
if (event.isComposing || event.keyCode === 229) {
return;
}
form.requestSubmit();
}
});
策略 2:event.timeStamp 視窗解決方法 (替代方案)
如果程式碼集嚴格禁止使用 keyCode 等已淘汰的 API,或在特定目標環境中,229 檢查有已知問題,則可改為追蹤瀏覽器回報的事件調度時間戳記。
Safari 傳送確認 Enter keydown 事件的時間與 compositionend 事件非常接近 (通常在 5 毫秒內,有時 keydown 時間戳記會因處理常式傳送順序反轉而略為提前),因此檢查時間差非常可靠,在標準條件下不太可能在行動虛擬鍵盤上觸發偽陽性。
let lastCompositionEndAt = null;
textarea.addEventListener('compositionend', (event) => {
// IMPORTANT: Always use event.timeStamp, not Date.now() or performance.now().
// Handler-time measurements are vulnerable to drift when the main thread is blocked.
lastCompositionEndAt = event.timeStamp;
});
textarea.addEventListener('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
event.preventDefault();
if (event.isComposing) {
return;
}
// Block submission if the event occurs within a 50ms window of composition ending.
// Math.abs handles Safari's inverted event delivery timing bug.
if (
lastCompositionEndAt !== null &&
Math.abs(event.timeStamp - lastCompositionEndAt) < 50
) {
return;
}
form.requestSubmit();
}
});