Nhiều giao diện trò chuyện gửi tin nhắn khi người dùng nhấn phím Enter trong <textarea>.
Cách này phù hợp với người dùng nhập trực tiếp bằng bàn phím Latinh (ví dụ: tiếng Anh), nhưng không phù hợp với người dùng nhập bằng Trình chỉnh sửa phương thức nhập (IME) để soạn văn bản bằng các ngôn ngữ như tiếng Nhật, tiếng Trung hoặc tiếng Hàn.
Trong những trường hợp này, phím Enter/Return được dùng để xác nhận ký tự chuyển đổi hiện tại. Nếu JavaScript tuỳ chỉnh lắng nghe keydown trên Enter và gửi ngay lập tức, thì tin nhắn của người dùng sẽ được gửi trong khi họ vẫn đang chuyển đổi ký tự, dẫn đến tin nhắn không đầy đủ, bị phân mảnh hoặc không chính xác.
Việc kiểm tra trạng thái hoạt động của thành phần áp dụng cho mọi trình xử lý keydown tuỳ chỉnh hoạt động trên Enter, chứ không chỉ cho các trường <textarea> nhiều dòng. Các trường <input> một dòng có cách xử lý Enter riêng, chẳng hạn như thanh tìm kiếm, trường đổi tên cùng dòng, trường hộp thoại phương thức và bảng lệnh, sẽ nhận được thao tác nhấn phím Enter xác nhận IME giống như <textarea>, trong đó isComposing được đặt thành true. Việc gửi ngầm ẩn gốc của <form> từ <input> mà không có trình xử lý Enter tuỳ chỉnh không cần xử lý rõ ràng, vì trình duyệt sẽ chặn việc gửi ngầm ẩn khi thao tác nhấn phím Enter được IME sử dụng.
Chiến lược triển khai
Đối với <textarea> hoặc <input> có tính năng nhấn Enter để gửi tuỳ chỉnh, hãy kiểm tra thuộc tính isComposing gốc của KeyboardEvent trước khi gửi nội dung. Đồng thời, hãy gọi event.preventDefault(): hành động mặc định của Enter là chèn một dòng mới trong <textarea> và trong <input> bên trong <form>, hành động này sẽ kích hoạt tính năng gửi ngầm định của trình duyệt, tức là gửi lần thứ hai.
<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();
}
});
Đối với <input> một dòng, hãy sử dụng cùng một trình xử lý mà không có điều kiện !event.shiftKey. Điều kiện này chỉ tồn tại vì Shift+Enter chèn một dòng mới vào <textarea>.
Hỗ trợ tiếp cận và kiểm thử
- Nút gửi rõ ràng: Luôn thêm phần tử
<button type="submit">. Phím tắt là trình trợ giúp; chúng không được thay thế đường dẫn gửi biểu mẫu gốc. - Thông tin đầu vào dễ tiếp cận: Đảm bảo tất cả phần tử
<input>và<textarea>đều được liên kết theo phương thức lập trình với một<label>bằng cách sử dụng các thuộc tínhidvàforphù hợp.
Chiến lược dự phòng
Trong các phiên bản Safari gần đây, một vấn đề về thứ tự sự kiện (lỗi WebKit 165004) đã phân phối compositionend đến trình xử lý tập lệnh trước khi xác nhận Enter keydown, mặc dù các sự kiện cơ bản được gửi theo thứ tự ngược lại. Khi trình xử lý keydown chạy, event.isComposing đã được đặt lại thành false, nghĩa là chỉ riêng quy trình kiểm tra tiêu chuẩn sẽ không ngăn được việc gửi sớm.
Nếu bạn cần hỗ trợ khả năng tương thích trên nhiều trình duyệt trên các phiên bản Safari cũ và các nền tảng khác, hãy áp dụng một trong các chiến lược dự phòng sau:
Chiến lược 1: Thêm một quy trình kiểm tra keyCode === 229 (nên dùng)
Bằng cách ghép nối event.isComposing với một quy trình kiểm tra event.keyCode === 229, bạn có thể phát hiện một cách đáng tin cậy sự kiện nhấn phím Enter xác nhận không theo thứ tự của Safari. Vì được lồng trong cổng event.key === 'Enter', nên thao tác này an toàn trước các bàn phím ảo trên thiết bị di động có thể sử dụng 229 để nhập bố cục ký tự thông thường.
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();
}
});
Chiến lược 2: Giải pháp thay thế cho cửa sổ event.timeStamp
Đối với những cơ sở mã nghiêm cấm việc sử dụng các API không dùng nữa như keyCode, hoặc nếu có vấn đề đã biết với quy trình kiểm tra 229 trong các môi trường được nhắm mục tiêu cụ thể, thì bạn có thể theo dõi dấu thời gian gửi sự kiện do trình duyệt báo cáo.
Vì Safari gửi sự kiện Enter keydown xác nhận cực kỳ gần với sự kiện compositionend (thường trong vòng 5 mili giây và đôi khi dấu thời gian keydown sớm hơn một chút do thứ tự phân phối trình xử lý bị đảo ngược), nên việc kiểm tra chênh lệch thời gian là rất đáng tin cậy và không có khả năng kích hoạt kết quả dương tính giả trên bàn phím ảo di động trong điều kiện tiêu chuẩn.
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();
}
});