- Пример использования
focus/blur - HTML5 и CSS3 вместо
focus/blur - Метод
elem.focus() - Разрешаем фокус на любом элементе:
tabindex - Событие
blurи методelem.blur() - Делегирование с
focus/blur - Итого
Событие 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» для навигации, и ваше хорошее отношение к ним будет вознаграждено ![]()
Некоторые браузеры выделяют элемент с фокусом пунктирной рамочкой. В примере выше это выделение убрано при помощи 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 способа сфокусироваться на элементе:
- Кликнуть мышью
- Перейти на него клавишей Tab
- Вызвать
elem.focus()
- По умолчанию, на многие элементы не могут получить фокус. Например, если вы кликните по
DIV, то фокусировка на нем не произойдет.Но это можно изменить, если поставить элементу атрибут
tabIndex. Этот атрибут также дает возможность контролировать порядок перехода при нажатии «Tab». - События
focusиblurне всплывают.В случаях, когда всплытие необходимо, нужно использовать
focusin/focusoutв IE иfocus/blurна стадии захвата для других браузеров.
Кликните по мышонку. Затем нажимайте клавиши со стрелками и он будет двигаться.
Обратите внимание, мышонок изначально находится в DIV с position:relative! Как его двигать?
Исходный документ и мышка ждут вас тут: tutorial/browser/events/mousie-src/index.html.
Самый естественный алгоритм решения:
- При клике мышонок получает фокус. Для этого нужно либо заменить
DIVна другой тег, либо добавить емуtabindex="-1". - Когда на элементе фокус, то клавиатурные события будут срабатывать прямо на нём. То есть, ловим
mousie.onkeydown.Мы выбираем
keydown, потому что он позволяет во-первых отлавливать нажатия на спец. клавиши (стрелки), а во-вторых, отменить действие браузера, которым по умолчанию является прокрутка страницы. - При нажатии на стрелки двигаем мышонка через
position:absoluteиtop/left.
Дальше решение — попробуйте сделать сами. Возможны подводные камни ![]()
-
При получении фокуса — готовим мышонка к перемещению:
document.getElementById('mousie').onfocus = function() { this.style.position = 'relative'; this.style.left = '0px'; this.style.top = '0px'; } - Коды для клавиш стрелок можно узнать, нажимая на них на тестовом стенде. Вот они: 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 являются координатами не относительно документа, а относительно позиционированного предка.
Что делать? Решений три.
- Первое — учесть этого позиционированного предка при вычислении
left/top, вычитать его координаты из координат относительно документа. - Второе — сделать
position:fixed. При этом координаты мышонка можно взять напрямую из mousie.getBoundingClientRect(), т.е. все вычисления выполнять относительно окна. Это больше компьютерно-игровой подход, чем работа с документом. - Третье — переместить мышонка под
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. При загрузке страницы не известно, включён он или нет.
Но мы можем догадаться о его состоянии из событий:
- Проверив символ, полученный по
keypress. Символ в верхнем регистре без нажатого Shift означает, что включён CapsLock. Аналогично, символ в нижнем регистре, но с Shift говорят о включенном CapsLock. Свойствоevent.shiftKeyпоказывает, нажат ли Shift. Так мы можем точно узнать, нажат ли Caps Lock. - Проверять
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, чтобы уберечь его от неправильного ввода.
- Для начала, когда пользователь сфокусировался на поле, мы должны вывести предупреждение о CapsLock, если он включен.
- Пользователь начинает ввод. Каждое событие
keypressвсплывает до обработчикаdocument.keypress, который обновляет состояниеcapsLockEnabled.Мы не можем использовать событие
input.onkeypress, для отображения состояния пользователю, потому что оно сработает доdocument.onkeypress(из-за всплытия) и, следовательно, до того, как мы узнаем состояние CapsLock.Есть много способов решить эту проблему. Можно, например, назначить обработчик состояния CapsLock на событие
input.onkeyup. То есть, индикация будет с задержкой, но это несущественно.Альтернативное решение — добавить на
inputтакой же обработчик, как и наdocument.onkeypress. - ..И наконец, пользователь убирает фокус с поля. Предупреждение может быть видно, если 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.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.