Элемент получает фокус, когда пользователь кликает по нему или использует клавишу Tab. Также существует HTML-атрибут autofocus, который устанавливает фокус на элемент, когда страница загружается. Есть и другие способы получения фокуса, о них – далее.

Фокусировка обычно означает: «приготовься к вводу данных на этом элементе», это хороший момент, чтобы инициализовать или загрузить что-нибудь.

Момент потери фокуса («blur») может быть важнее. Это момент, когда пользователь кликает куда-то ещё или нажимает Tab, чтобы переключиться на следующее поле формы. Есть другие причины потери фокуса, о них – далее.

Потеря фокуса обычно означает «данные введены», и мы можем выполнить проверку введённых данных или даже отправить эти данные на сервер и так далее.

В работе с событиями фокусировки есть важные особенности. Мы постараемся разобрать их далее.

События focus/blur

Событие focus вызывается в момент фокусировки, а blur – когда элемент теряет фокус.

Используем их для валидации(проверки) введённых данных.

В примере ниже:

  • Обработчик blur проверяет, введён ли email, и если нет – показывает ошибку.
  • Обработчик focus скрывает это сообщение об ошибке (в момент потери фокуса проверка повторится):
<style>
  .invalid { border-color: red; }
  #error { color: red }
</style>

Ваш email: <input type="email" id="input">

<div id="error"></div>

<script>
input.onblur = function() {
  if (!input.value.includes('@')) { // не email
    input.classList.add('invalid');
    error.innerHTML = 'Пожалуйста, введите правильный email.'
  }
};

input.onfocus = function() {
  if (this.classList.contains('invalid')) {
    // удаляем индикатор ошибки, т.к. пользователь хочет ввести данные заново
    this.classList.remove('invalid');
    error.innerHTML = "";
  }
};
</script>

Современный HTML позволяет делать валидацию с помощью атрибутов required, pattern и т.д. Иногда – это всё, что нам нужно. JavaScript можно использовать, когда мы хотим больше гибкости. А ещё мы могли бы отправлять изменённое значение на сервер, если оно правильное.

Методы focus/blur

Методы elem.focus() и elem.blur() устанавливают/снимают фокус.

Например, запретим посетителю переключаться с поля ввода, если введённое значение не прошло валидацию:

<style>
  .error {
    background: red;
  }
</style>

Ваш email: <input type="email" id="input">
<input type="text" style="width:280px" placeholder="введите неверный email и кликните сюда">

<script>
  input.onblur = function() {
    if (!this.value.includes('@')) { // не email
      // показать ошибку
      this.classList.add("error");
      // ...и вернуть фокус обратно
      input.focus();
    } else {
      this.classList.remove("error");
    }
  };
</script>

Это сработает во всех браузерах, кроме Firefox (bug).

Если мы что-нибудь введём и нажмём Tab или кликнем в другое место, тогда onblur вернёт фокус обратно.

Отметим, что мы не можем «отменить потерю фокуса», вызвав event.preventDefault() в обработчике onblur потому, что onblur срабатывает после потери фокуса элементом.

Потеря фокуса, вызванная JavaScript

Потеря фокуса может произойти по множеству причин.

Одна из них – когда посетитель кликает куда-то ещё. Но и JavaScript может быть причиной, например:

  • alert переводит фокус на себя – элемент теряет фокус (событие blur), а когда alert закрывается – элемент получает фокус обратно (событие focus).
  • Если элемент удалить из DOM, фокус также будет потерян. Если элемент добавить обратно, то фокус не вернётся.

Из-за этих особенностей обработчики focus/blur могут сработать тогда, когда это не требуется.

Используя эти события, нужно быть осторожным. Если мы хотим отследить потерю фокуса, которую инициировал пользователь, тогда нам следует избегать её самим.

Включаем фокусировку на любом элементе: tabindex

Многие элементы по умолчанию не поддерживают фокусировку.

Какие именно – зависит от браузера, но одно всегда верно: поддержка focus/blur гарантирована для элементов, с которыми посетитель может взаимодействовать: <button>, <input>, <select>, <a> и т.д.

С другой стороны, элементы форматирования <div>, <span>, <table> – по умолчанию не могут получить фокус. Метод elem.focus() не работает для них, и события focus/blur никогда не срабатывают.

Это можно изменить HTML-атрибутом tabindex.

Цель этого атрибута – указать порядковый номер элемента, когда клавиша Tab используется для переключения между элементами.

То есть: если у нас два элемента, первый имеет tabindex="1", а второй tabindex="2", то находясь в первом элементе и нажав Tab – мы переместимся во второй.

Есть два специальных значения:

  • tabindex="0" делает элемент последним.
  • tabindex="-1" значит, что Tab игнорирует этот элемент.

Любой элемент поддерживает фокусировку, если имеет tabindex.

Например, список ниже. Кликните первый пункт в списке и нажмите Tab:

Кликните первый пункт в списке и нажмите Tab. Продолжайте следить за порядком. Обратите внимание, что много последовательных нажатий Tab могут вывести фокус из iframe с примером.
<ul>
  <li tabindex="1">Один</li>
  <li tabindex="0">Ноль</li>
  <li tabindex="2">Два</li>
  <li tabindex="-1">Минус один</li>
</ul>

<style>
  li { cursor: pointer; }
  :focus { outline: 1px dashed green; }
</style>

Порядок такой: 1 - 2 - 0 (ноль всегда последний). Обычно <li> не поддерживает фокусировку, но tabindex полностью включает её, а также события и стилизацию псевдоклассом :focus.

elem.tabIndex тоже работает

Мы можем добавить tabindex из JavaScript, используя свойство elem.tabIndex. Это даст тот же эффект.

События focusin/focusout

События focus и blur не всплывают.

Например, мы не можем использовать onfocus на <form>, чтобы подсветить её:

<!-- добавить класс при фокусировке на форме -->
<form onfocus="this.className='focused'">
  <input type="text" name="name" value="Имя">
  <input type="text" name="surname" value="Фамилия">
</form>

<style> .focused { outline: 1px solid red; } </style>

Пример выше не работает, потому что когда пользователь перемещает фокус на <input>, событие focus срабатывает только на этом элементе. Это событие не всплывает. Следовательно, form.onfocus никогда не срабатывает.

У этой проблемы два решения.

Первое: забавная особенность – focus/blur не всплывают, но передаются вниз на фазе перехвата.

Это сработает:

<form id="form">
  <input type="text" name="name" value="Имя">
  <input type="text" name="surname" value="Фамилия">
</form>

<style> .focused { outline: 1px solid red; } </style>

<script>
  // установить обработчик на фазе перехвата (последний аргумент true)
  form.addEventListener("focus", () => form.classList.add('focused'), true);
  form.addEventListener("blur", () => form.classList.remove('focused'), true);
</script>

Второе решение: события focusin и focusout – такие же, как и focus/blur, но они всплывают.

Заметьте, что эти события должны использоваться с elem.addEventListener, но не с on<event>.

Второй рабочий вариант:

<form id="form">
  <input type="text" name="name" value="Имя">
  <input type="text" name="surname" value="Фамилия">
</form>

<style> .focused { outline: 1px solid red; } </style>

<script>
  form.addEventListener("focusin", () => form.classList.add('focused'));
  form.addEventListener("focusout", () => form.classList.remove('focused'));
</script>

Итого

События focus и blur срабатывают на фокусировке/потере фокуса элемента.

Их особенности:

  • Они не всплывают. Но можно использовать фазу перехвата или focusin/focusout.
  • Большинство элементов не поддерживают фокусировку по умолчанию. Используйте tabindex, чтобы сделать фокусируемым любой элемент.

Текущий элемент с фокусом можно получить из document.activeElement.

Задачи

важность: 5

Создайте <div>, который превращается в <textarea>, если на него кликнуть.

<textarea> позволяет редактировать HTML в элементе <div>.

Когда пользователь нажимает Enter или переводит фокус, <textarea> превращается обратно в <div>, и его содержимое становится HTML-кодом в <div>.

Демо в новом окне

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

важность: 5

Сделайте ячейки таблицы редактируемыми по клику.

  • По клику – ячейка должна стать «редактируемой» (textarea появляется внутри), мы можем изменять HTML. Изменение размера ячейки должно быть отключено.
  • Кнопки OK и ОТМЕНА появляются ниже ячейки и, соответственно, завершают/отменяют редактирование.
  • Только одну ячейку можно редактировать за один раз. Пока <td> в «режиме редактирования», клики по другим ячейкам игнорируются.
  • Таблица может иметь множество ячеек. Используйте делегирование событий.

Демо:

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

  1. По клику – заменить innerHTML ячейки на <textarea> с теми же размерами и без рамки. Можно использовать JavaScript или CSS, чтобы установить правильный размер.
  2. Присвоить textarea.value значение td.innerHTML.
  3. Установить фокус на текстовую область.
  4. Показать кнопки ОК/ОТМЕНА под ячейкой, обрабатывать клики по ним.

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

важность: 4

Установите фокус на мышь. Затем используйте клавиши со стрелками, чтобы её двигать:

Демо в новом окне

P.S. Не добавляйте обработчики никуда, кроме элемента #mouse. P.P.S. Не изменяйте HTML/CSS, подход должен быть общим и работать с любым элементом.

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

Мы можем использовать mouse.onclick для обработки клика и сделать мышь «перемещаемой» с помощью position:fixed, а затем использовать mouse.onkeydown для обработки клавиш со стрелками.

Единственная проблема в том, что keydown срабатывает только на элементах с фокусом. И нам нужно добавить tabindex к элементу. Так как изменять HTML запрещено, то для этого мы можем использовать свойство mouse.tabIndex.

P.S. Мы также можем заменить mouse.onclick на mouse.onfocus.

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

Карта учебника

Комментарии

перед тем как писать…
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.