Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

События и методы "focus/blur"

  1. Пример использования focus/blur
  2. HTML5 и CSS3 вместо focus/blur
    1. Подсветка при фокусировке
    2. Автофокус
    3. Плейсхолдер
  3. Метод elem.focus()
  4. Разрешаем фокус на любом элементе: tabindex
  5. Событие blur и метод elem.blur()
  6. Делегирование с focus/blur
    1. Всплывающие альтернативы: focusin/focusout
  7. Итого

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

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

По умолчанию, не все элементы поддерживают эти события. Перечень элементов немного разнится от браузера к браузеру, например, список для IE описан в MSDN. Но есть элементы, которые поддерживают фокусировку всегда. В первую очередь это те, c которыми посетитель может взаимодействовать: INPUT, SELECT, A и т.п.

С другой стороны, элементы для форматирования, такие как DIV, SPAN, TABLE и другие — по умолчанию не поддерживают это событие, т.е. на них нельзя «фокусироваться». Впрочем, существует способ включить фокусировку и для них.

Пример использования focus/blur

Поле из примера ниже при фокусировке(кликните на него) становится подсвеченным, а при снятии фокуса(клик в другом месте) теряет подсветку:

<input type="text">

<style> 
.highlight { background: #FA6; } 
</style>

<script>
var input = document.getElementsByTagName('input')[0];

*!*input.onfocus*/!* = function() {
  this.className = 'highlight';
}

*!*input.onblur*/!* = function() {
  this.className = '';
}
</script>

Этот пример — учебный. В современных браузерах для этого можно обойтись и без JavaScript.

HTML5 и CSS3 вместо focus/blur

Некоторые задачи могут быть решены средствами современного HTML/CSS. На текущий момент единственный браузер, который не очень поддерживает соответствующие свойства — это IE<10.

Поэтому, прежде, чем переходить к JavaScript, мы рассмотрим три примера.

Подсветка при фокусировке

Стилизация полей ввода при фокусировке во всех браузерах, кроме IE<8, может быть решена средствами CSS (CSS2.1), а именно — селектором :focus:

<style> 
*!*input:focus*/!* { background: #FA6; } 
</style>
<input type="text"> 

<p>Селектор :focus выделит элемент при фокусировке на нем (кроме IE<8).</p>

Автофокус

При загрузке страницы, если на ней существует элемент с атрибутом autofocus — браузер автоматически фокусируется на этом элементе. Работает во всех браузерах, кроме IE<10.

<input type="text" name="search" *!*autofocus*/!*>
Открыть в новом окне

Кросс-браузерно можно сделать вызовом JavaScript:

<input type="text" name="search">
<script> 
  document.getElementsByName('search')[0].focus();
</script>

Плейсхолдер

Плейсхолдер — это значение-подсказка внутри INPUT, которое автоматически исчезает при фокусировке и существует, пока посетитель не начал вводить текст.

Во всех браузерах, кроме IE<10, это реализуется специальным атрибутом placeholder:

<input type="text" placeholder="E-mail"> Работает везде, кроме IE до 10

В некоторых браузерах этот текст можно стилизовать:

<style>
.my*!*::-webkit-input-placeholder*/!*, .my*!*::-moz-placeholder*/!* {
  color: red;
  font-style: italic;
}
</style>

<input class="my" type="text" placeholder="E-mail"> 
Стилизуется в Safari/Chrome/FF

Реализуйте плейсхолдер на JavaScript, чтобы он работал в IE<10.

Правила работы плейсхолдера:

  • Элемент изначально содержит плейсхолдер. Специальный класс placeholder придает ему серый цвет.
  • При фокусировке плейсхолдер пропадает.
  • При снятии фокуса, если поле пустое — плейсхолдер возвращается.

Все три пункта должны быть соблюдены кросс-браузерно. Пример:

Исходный код: tutorial/browser/events/placeholder-src/index.html

Решение
Решение

Состояние элемента определяется наличием класса placeholder. Для простоты будем считать, что это — единственный возможный класс у INPUT'а

При фокусировке, если в элементе находится плейсхолдер — он должен исчезать:

input.onfocus = function() {
  if (this.className == 'placeholder') {
    prepareInput(this);
  }
}

function prepareInput(input) { // превратить элемент в простой пустой input
  input.className = '';
  input.value = '';
}

… Затем элемент потеряет фокус. При этом нужно возвратить плейсхолдер, но только в том случае, если элемент пустой:

input.onblur = function() {
  if (this.value == '') { // если пустой
    resetInput(this); // заполнить плейсхолдером
  }
}

function resetInput(input) {
  input.className = 'placeholder';
  input.value = 'E-mail';
}

Это решение можно сделать удобнее в поддержке, если при выполнении prepareInput копировать значение в специальное свойство, а в resetInput — восстанавливать его:

function prepareInput(input) {
  input.className = '';
*!*
  input.oldValue = input.value;
*/!*
  input.value = '';
}

function resetInput(input) {
  input.className = 'placeholder';
  input.value = input.oldValue;
}

Теперь, если понадобится изменить значение плейсхолдера — это достаточно сделать в HTML, и не надо трогать JavaScript-код.

Полное решение: tutorial/browser/events/placeholder/index.html

Метод elem.focus()

На элементе, который поддерживает фокусировку, можно сфокусироваться программно.

Например, можно показать форму и тут же сфокусироваться на ее поле, чтобы посетитель мог вводить текст:

<form style="display:none" name="form">
  Искать <input style="text" name="search">
</form>

<input type="button" value="Показать форму поиска" onclick="showForm()">

<script>
function showForm() {
  var form = document.forms.form;
  form.style.display = 'block';
*!*
  form.elements.search.focus();
*/!*
}
</script>

Разрешаем фокус на любом элементе: tabindex

Несмотря на то, что не все элементы могут иметь фокус по умолчанию, существует атрибут tabindex, который позволяет устранить подобную несправедливость.

Когда вы присваиваете tabindex="<число>" элементу:

  • Появляется возможность на нем сфокусироваться.
  • Пользователь при нажатии клавиши «Tab» переходит от элемента с наименьшим положительным значением tabindex к следующему.

    Исключением является специальное значение tabindex="0", которое делает элемент всегда последним.

  • Значение tabindex=-1 означает, на элементе можно фокусироваться, но только программно. Клавиша «Tab» будет его игнорировать. Сработает только метод focus().

В примере ниже, есть список элементов. Кликните на любой из них и нажмите «tab».

Кликните на первый элемент списка и нажмите Tab. Осторожно, Tab может вывести вас за границы примера.
<ul>
<li tabindex="1" onfocus="showFocus(this)">Один</li>
<li tabindex="0" onfocus="showFocus(this)">Ноль</li>
<li tabindex="2" onfocus="showFocus(this)">Два</li>
<li tabindex="-1" onfocus="showFocus(this)">Минус один</li>
</ul>

<style> :focus { border: 1px dashed green; outline: 0; } </style>

Порядок перемещения по клавише «Tab» в примере выше должен быть таким: 1 - 2 - 0 (ноль всегда последний). Продвинутые пользователи частенько используют «Tab» для навигации, и ваше хорошее отношение к ним будет вознаграждено Smile

Некоторые браузеры выделяют элемент с фокусом пунктирной рамочкой. В примере выше это выделение убрано при помощи CSS-свойства outline: 0 и заменено собственной рамкой.

Событие blur и метод elem.blur()

Событие blur — это противоположность focus. Событие срабатывает, когда элемент теряет фокус. Метод elem.blur() заставляет элемент потерять фокус, если таковой имеется.

Зачастую это событие используется для проверки («валидации») значения

В следующем примере, поле ввода проходит валидацию при потере фокуса. Наберите что-нибудь в поле «возраст» примера ниже и завершите ввод, нажав Tab или кликнув в другое место страницы.

Введенное значение будет автоматически проверено:

<label>Введите ваш возраст: <input type="text" name="age"></label>

<div id="error">здесь будет ошибка, если вы введёте не число</div>
<style> .error { background: red; } </style>

<script>
var input = document.getElementsByName('age')[0];
var errorHolder = document.getElementById('error');

*!*input.onblur*/!* = function() {
  if (isNaN(this.value)) { // введено не число
    // показать ошибку
    this.className = 'error';
    errorHolder.innerHTML = 'Вы ввели не число. Исправьте, пожалуйста'
  }
}

*!*input.onfocus*/!* = function() { 
  // сбросить состояние "ошибка", если оно есть
  if (this.className == 'error') {
    this.className = '';
    errorHolder.innerHTML = '';
  }
}
</script>

Здесь кроме blur использован также обработчик события focus, чтобы повторная фокусировка снимала состояние «ошибки».

Делегирование с focus/blur

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

Таким образом, мы не можем использовать делегирование с ними. Например, мы не можем сделалать так, чтобы при фокусировке в форме она подсвечивалась:

<form *!*onfocus="this.className='focused'"*/!*>
  <input type="text" name="name" value="Ваше имя">
  <input type="text" name="surname" value="Ваша фамилия">
</form>

<style> .focused { border: 2px solid red; } </style>

В примере выше стоит обработчик onfocus на форме, но он не работает, т.к. при фокусировке на любом INPUT событие focus срабатывает только на этом элементе и не всплывает наверх.

Что делать? Неужели мы должны присваивать обработчик каждому полю или все-таки есть возможность использовать делегирование?

Всплывающие альтернативы: focusin/focusout

  • В современных стандартах есть события focusin/focusout — тоже самое, что и focus/blur, только они всплывают.

    Поддерживается в IE6+, а также в Safari/Chrome и Opera. Во всех браузерах, кроме IE, должны быть при назначены не через on-свойство, а помощи elem.addEventListener.

    Единственный современный браузер, который их не поддерживает — это Firefox.

  • Во всех браузерах, кроме IE<9, события focus/blur можно поймать на стадии захвата, то есть указав последний аргумент true для addEventListener вместо обычного false.

Как правило, используют две ветки кода: focus/blur ловят на фазе захвата везде, кроме старых IE, в которых используют focusin/focusout.

Подсветка формы в примере ниже работает во всех браузерах.

<form name="form">
  <input type="text" name="name" value="Ваше имя">
  <input type="text" name="surname" value="Ваша фамилия">
</form>
<style>
.focused { border: 2px solid red; }
</style>

<script>
function onFormFocus() { this.className = 'focused'; }
function onFormBlur() { this.className = ''; }

var form = document.forms.form;

if (form.addEventListener) { 
  form.addEventListener('focus', onFormFocus, true); 
  form.addEventListener('blur', onFormBlur, true); 
} else {  // IE<9
  form.onfocusin = onFormFocus;
  form.onfocusout = onFormBlur;
}
</script>

Итого

Есть 3 способа сфокусироваться на элементе:

  1. Кликнуть мышью
  2. Перейти на него клавишей Tab
  3. Вызвать elem.focus()
  • По умолчанию, на многие элементы не могут получить фокус. Например, если вы кликните по DIV, то фокусировка на нем не произойдет.

    Но это можно изменить, если поставить элементу атрибут tabIndex. Этот атрибут также дает возможность контролировать порядок перехода при нажатии «Tab».

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

    В случаях, когда всплытие необходимо, нужно использовать focusin/focusout в IE и focus/blur на стадии захвата для других браузеров.

Кликните по мышонку. Затем нажимайте клавиши со стрелками и он будет двигаться.

Обратите внимание, мышонок изначально находится в DIV с position:relative! Как его двигать?

Исходный документ и мышка ждут вас тут: tutorial/browser/events/mousie-src/index.html.

Алгоритм
Решение
Алгоритм

Самый естественный алгоритм решения:

  1. При клике мышонок получает фокус. Для этого нужно либо заменить DIV на другой тег, либо добавить ему tabindex="-1".
  2. Когда на элементе фокус, то клавиатурные события будут срабатывать прямо на нём. То есть, ловим mousie.onkeydown.

    Мы выбираем keydown, потому что он позволяет во-первых отлавливать нажатия на спец. клавиши (стрелки), а во-вторых, отменить действие браузера, которым по умолчанию является прокрутка страницы.

  3. При нажатии на стрелки двигаем мышонка через position:absolute и top/left.

Дальше решение — попробуйте сделать сами. Возможны подводные камни Smile

Решение
Решение
  1. При получении фокуса — готовим мышонка к перемещению:
    document.getElementById('mousie').onfocus = function() {
      this.style.position = 'relative';
      this.style.left = '0px';
      this.style.top = '0px';
    }
    
  2. Коды для клавиш стрелок можно узнать, нажимая на них на тестовом стенде. Вот они: 37-38-39-40 (влево-вверх-вправо-вниз).

    При нажатии стрелки — двигаем мышонка:

    document.getElementById('mousie').onkeydown = function(e) {
      e = e || event;
      switch(e.keyCode) {
      case 37: // влево
        this.style.left = parseInt(this.style.left)-this.offsetWidth+'px';
        return false;
      case 38: // вверх
        this.style.top = parseInt(this.style.top)-this.offsetHeight+'px';
        return false;
      case 39: // вправо
        this.style.left = parseInt(this.style.left)+this.offsetWidth+'px';
        return false;
      case 40: // вниз
        this.style.top = parseInt(this.style.top)+this.offsetHeight+'px';
        return false;
      }
    }
    
    Обратите внимание, что действием по умолчанию для стрелок является прокрутка страницы. Поэтому, что ее отменить, нужно использовать return false.

    Когда пользователь убирает фокус с мышки, то она перестает реагировать на клавиши. Нет нужды удалять обработчики на blur, потому что браузер перестанет вызывать keydown.

В решении выше есть проблема. Мышонок находится в DIV с position:relative. Это означает, что его left/top являются координатами не относительно документа, а относительно позиционированного предка.

Что делать? Решений три.

  1. Первое — учесть этого позиционированного предка при вычислении left/top, вычитать его координаты из координат относительно документа.
  2. Второе — сделать position:fixed. При этом координаты мышонка можно взять напрямую из mousie.getBoundingClientRect(), т.е. все вычисления выполнять относительно окна. Это больше компьютерно-игровой подход, чем работа с документом.
  3. Третье — переместить мышонка под document.body в начале движения. Тогда и с координатами всё будет в порядке. Но при этом могут «слететь» стили.

Хочется верить, что первое и второе решения понятны. А вот третье более интересно, так как скрывает новые тонкости.

Если пойти этим путём, то в обработчик onfocus следует добавить перемещение мышонка под BODY:

document.getElementById('mousie').onfocus = function() {
  var coords = getCoords(this);

*!*
  document.body.appendChild(this);
*/!*
  
  this.style.position = 'absolute';
  this.style.left = coords.left + 'px';
  this.style.top = coords.top + 'px';
};

…Но вот беда! При document.body.appendChild(this) с элемента слетает фокус!

Фокус нужно восстановить, чтобы ловить keydown. Однако некоторые браузеры, например FF и IE, не дают вызвать метод focus() элемента из его обработчика onfocus. То есть, сделать это нельзя.

Чтобы это обойти, можно поставить обработчик не onfocus, а onclick:

document.getElementById('mousie').onclick = function() {
  var coords = getCoords(this);
  this.style.position = 'absolute';
  this.style.left = coords.left + 'px';
  this.style.top = coords.top + 'px';

*!*
  if (this.parentNode != document.body) {
    document.body.appendChild(this);
    this.focus();
  }
*/!*
};

Обычно событие focus всё равно происходит после click, но здесь элемент перемещается, поэтому оно «съедается» и мы инициируем его сами вызовом focus().

Окончательное решение: tutorial/browser/events/mousie/index.html.

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

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

Исходный документ: tutorial/browser/events/capslock-src/index.html.

Алгоритм
Решение
Алгоритм

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

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

  1. Проверив символ, полученный по keypress. Символ в верхнем регистре без нажатого Shift означает, что включён CapsLock. Аналогично, символ в нижнем регистре, но с Shift говорят о включенном CapsLock. Свойство event.shiftKey показывает, нажат ли Shift. Так мы можем точно узнать, нажат ли Caps Lock.
  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) {
  e = e || event; 

  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 (nagivator.platform.substr(0,3) != 'Mac') { // событие для CapsLock глючит под Mac
  document.onkeydown = function(e) {
    e = e || event;
  
    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>

Полный код решения: tutorial/browser/events/capslock/index.html.

Создайте DIV, который при нажатии Ctrl-E превращается в TEXTAREA.
Изменения, внесенные в поле, можно сохранить в DIV сочетанием клавиш Ctrl-S или же отменить нажав Esc. После этого, TEXTAREA снова превращается в DIV.

Содержимое, сохраненное как HTML-теги должно работать.

Посмотрите, как оно должно работать: tutorial/browser/events/hotfield/index.html.

Исходный код тут: tutorial/browser/events/hotfield-src/index.html.

Решение
Решение

Как видно из исходного кода, #view - это DIV, который будет содержать результат, а #area - это редактируемое текстовое поле.

Внешний вид

Так как мы преобразуем DIV в TEXTAREA и обратно, нам нужно сделать их практически одинаковыми с виду:

#view, #area {
  height:150px;
  width:400px;
  font-family:arial;
}

Текстовое поле нужно как-то выделить. Можно добавить границу, но тогда изменится блок: он увеличится в размерах и немного съедет текст.

Для того, чтобы сделать размер #area таким же, как и #view, добавим поля(padding):

#view {  
  /* padding + border = 3px */
  padding: 2px; 
  border:1px solid black; 
}

#area заменяет поля границами:

#area {
  border: 3px groove blue;  
  padding: 0px;

  display:none;
}

По умолчанию, текстовое поле скрыто. Кстати, этот код убирает дополнительную рамку в Chrome/Safari, которая появляется вокруг поля, когда на него попадает фокус:

#area:focus { 
  outline: none; /* убирает рамку в Safari при фокусе */
}

Коды клавиш

Чтобы отследить клавиши, нам нужны их скан-коды, а не символы. Это важно, потому что горячие клавиши должны работать независимо от языковой раскладки. Поэтому, мы будем использовать keydown:

document.onkeydown = function(e) {
  e = e || event;
  if (e.keyCode == 27) { // escape
    cancel();
    return false;
  }

  if ((e.ctrlKey && e.keyCode == 'E'.charCodeAt(0)) && !area.offsetHeight) {
    edit();
    return false;
  }

  if ((e.ctrlKey && e.keyCode == 'S'.charCodeAt(0)) && area.offsetHeight) {
    save();
    return false;
  }
}

В примере выше, offsetHeight используется для того, чтобы проверить, отображается элемент или нет. Это очень надежный способ для всех элементов, кроме TR в некоторых старых браузерах.

В отличие от простой проверки display=='none', этот способ работает с элементом, спрятанным с помощью стилей, а так же для элементов, у которых скрыты родители.

Редактирование

Следующие функции переключают режимы. HTML-код разрешен, поэтому возможна прямая трансформация в TEXTAREA и обратно.

function edit() {
    view.style.display = 'none';
    area.value = view.innerHTML;
    area.style.display = 'block';
    area.focus();
}

function save() {
    area.style.display = 'none';
    view.innerHTML = area.value;
    view.style.display = 'block';
}

function cancel() {
    area.style.display = 'none';
    view.style.display = 'block';
}

Полное решение: tutorial/browser/events/hotfield/index.html.
Чтобы проверить его, сфокусируйтесь на правом iframe, пожалуйста.

Создайте для <input type="password"> красивый, стилизованный, плейсхолдер, например (кликните на тексте):

Исходный документ: tutorial/browser/events/placeholder-html-src.html.

При клике плейсхолдер просто исчезает, и дальше не показывается.

Вёрстка
Решение
Вёрстка

Для вёрстки можно использовать отрицательный margin у текста с подсказкой.

Решение в плане вёрстка есть в решении задачи Расположить текст внутри INPUT.

Решение
Решение

placeholder.onclick = function() {
  input.focus();
}

/* это событие сработает 
   и вызове `input.focus()`
   и при прямом клике на input
*/
input.onfocus = function() {
  placeholder.parentNode && placeholder.parentNode.removeChild(placeholder);
}

Полный код: tutorial/browser/events/placeholder-html.html.


Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!