Вернуться к уроку

Поле, предупреждающее о включенном CapsLock

важность: 3

Создайте поле, которое будет предупреждать пользователя, если включен CapsLock. Выключение CapsLock уберёт предупреждение.

Такое поле может помочь избежать ошибок при вводе пароля.

Открыть песочницу для задачи.

Алгоритм

JavaScript не имеет доступа к текущему состоянию CapsLock. При загрузке страницы не известно, включён он или нет.

Но мы можем догадаться о его состоянии из событий:

  1. Проверив символ, полученный по keypress. Символ в верхнем регистре без нажатого Shift означает, что включён CapsLock. Аналогично, символ в нижнем регистре, но с Shift говорят о включенном CapsLock. Свойство event.shiftKey показывает, нажат ли Shift. Так мы можем точно узнать, нажат ли CapsLock.
  2. Проверять keydown. Если нажат CapsLock (скан-код равен 20), то переключить состояние, но лишь в том случае, когда оно уже известно. Под Mac так делать не получится, поскольку клавиатурные события с CapsLock работают некорректно.

Имея состояние CapsLock в переменной, можно при фокусировке на INPUT выдавать предупреждение.

Отслеживать оба события: keydown и keypress хорошо бы на уровне документа, чтобы уже на момент входа в поле ввода мы знали состояние CapsLock.

Но при вводе сразу в нужный input событие keypress событие доплывёт до document и поставит состояние CapsLock после того, как сработает на input. Как это обойти – подумайте сами.

Решение

При загрузке страницы, когда еще ничего не набрано, мы ничего не знаем о состоянии CapsLock, поэтому оно равно null:

var capsLockEnabled = null;

Когда нажата клавиша, мы можем попытаться проверить, совпадает ли регистр символа и состояние Shift:

document.onkeypress = function(e) {

  var chr = getChar(e);
  if (!chr) return; // специальная клавиша

  if (chr.toLowerCase() == chr.toUpperCase()) {
    // символ, который не имеет регистра, такой как пробел,
    // мы не можем использовать для определения состояния CapsLock
    return;
  }

  capsLockEnabled = (chr.toLowerCase() == chr && e.shiftKey) || (chr.toUpperCase() == chr && !e.shiftKey);
}

Когда пользователь нажимает CapsLock, мы должны изменить его текущее состояние. Но мы можем сделать это только если знаем, что был нажат CapsLock.

Например, когда пользователь открыл страницу, мы не знаем, включен ли CapsLock. Затем, мы получаем событие keydown для CapsLock. Но мы все равно не знаем его состояния, был ли CapsLock выключен или, наоборот, включен.

if (navigator.platform.substr(0, 3) != 'Mac') { // событие для CapsLock глючит под Mac
  document.onkeydown = function(e) {
    if (e.keyCode == 20 && capsLockEnabled !== null) {
      capsLockEnabled = !capsLockEnabled;
    }
  };
}

Теперь поле. Задание состоит в том, чтобы предупредить пользователя о включенном CapsLock, чтобы уберечь его от неправильного ввода.

  1. Для начала, когда пользователь сфокусировался на поле, мы должны вывести предупреждение о CapsLock, если он включен.

  2. Пользователь начинает ввод. Каждое событие keypress всплывает до обработчика document.keypress, который обновляет состояние capsLockEnabled.

    Мы не можем использовать событие input.onkeypress, для отображения состояния пользователю, потому что оно сработает до document.onkeypress (из-за всплытия) и, следовательно, до того, как мы узнаем состояние CapsLock.

    Есть много способов решить эту проблему. Можно, например, назначить обработчик состояния CapsLock на событие input.onkeyup. То есть, индикация будет с задержкой, но это несущественно.

    Альтернативное решение – добавить на input такой же обработчик, как и на document.onkeypress.

  3. …И наконец, пользователь убирает фокус с поля. Предупреждение может быть видно, если CapsLock включен, но так как пользователь уже ушел с поля, то нам нужно спрятать предупреждение.

Код проверки поля:

<input type="text" onkeyup="checkCapsWarning(event)" onfocus="checkCapsWarning(event)" onblur="removeCapsWarning()" />

<div style="display:none;color:red" id="caps">Внимание: нажат CapsLock!</div>

<script>
  function checkCapsWarning() {
    document.getElementById('caps').style.display = capsLockEnabled ? 'block' : 'none';
  }

  function removeCapsWarning() {
    document.getElementById('caps').style.display = 'none';
  }
</script>

Открыть решение в песочнице.