Во многих интерфейсах чатов сообщение отправляется, когда пользователь нажимает Enter в <textarea>.
Это работает для пользователей, которые вводят текст напрямую с латинской клавиатуры (например, на английском языке), но не работает для пользователей, которые вводят текст с помощью редактора метода ввода (IME) для составления текста на таких языках, как японский, китайский или корейский.
В этом случае клавиша Enter/Return используется для подтверждения текущего варианта преобразования символов. Если специальный код JavaScript прослушивает keydown в Enter и сразу отправляет сообщение, оно будет отправлено до того, как пользователь закончит преобразование символов. В результате сообщение будет неполным, фрагментированным или неправильным.
Проверка composition-active применяется к любому пользовательскому обработчику keydown, который воздействует на Enter, а не только к многострочным полям <textarea>. Однострочные поля <input> с собственной обработкой Enter, такие как строки поиска, встроенные поля переименования, поля модальных диалоговых окон и палитры команд, получают то же нажатие клавиши Enter, подтверждающее IME, что и <textarea>, со значением isComposing, установленным на true. Встроенная неявная отправка <form> из <input> без пользовательского обработчика Enter не требует явной обработки, поскольку браузеры подавляют неявную отправку, когда нажатие клавиши Enter используется IME.
Стратегия реализации
Для <textarea> или <input> с настраиваемой отправкой по нажатию Enter проверьте нативное свойство isComposing элемента KeyboardEvent перед отправкой контента. Также вызовите event.preventDefault(): действие по умолчанию Enter вставляет новую строку в <textarea>, а в <input> внутри <form> запускает неявную отправку браузера, которая приведет к повторной отправке.
<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>программно связаны с<label>с помощью соответствующих атрибутовidиfor.
Резервные стратегии
В последних версиях Safari из-за проблемы с порядком событий (ошибка WebKit 165004) обработчики скриптов получали compositionend до подтверждения Enter keydown, хотя базовые события отправлялись в обратном порядке. К моменту запуска обработчика keydown значение event.isComposing уже было сброшено до false, а значит, стандартная проверка не смогла бы предотвратить преждевременную отправку.
Если вам нужно обеспечить совместимость со старыми версиями Safari и другими платформами, используйте одну из следующих резервных стратегий:
Стратегия 1. Добавьте проверку keyCode === 229 (рекомендуется)
Сочетание event.isComposing с проверкой event.keyCode === 229 позволяет надежно отслеживать нажатие клавиши Enter в Safari. Поскольку этот код вложен в условие 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-дневным периодом (альтернативный вариант)
Если в вашей базе кода строго запрещено использовать устаревшие API, такие как keyCode, или если в определенных целевых средах возникают проблемы с проверкой 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();
}
});