Делаем компактные формы более доступными
Mike Brittain, 24 мая 2007
Создание удобных и доступных форм ставит перед разработчиком много вопросов, которые значительно усложняются, если нужно разместить форму в ограниченном пространстве. Компактные формы могут хорошо выглядеть на бумаге, но они игнорируют многие требования аксессибилити.
Недавно дизайнер попросил у меня создать компактную форму, по очень известной технологии, когда названия полей помещаются в них же.
Обычно для этого нужно присвоить атрибуту value элемента input значение имени поля. Использую немного JavaScript и серверных скриптов можно гарантировать, что пользователь не подтвердит форму со значениями «Логин» и «Пароль». Чтобы скрыть пароль от посторонних глаз, в поле для его ввода символы заменяются на звездочки и точки, и это не позволяет нам поместить в это поле полезный текст, но даже если бы этой проблемы не было использование значений по умолчанию вместо меток значительно ухудшают аксессибилити формы.
В этой статье мы создадим форму которая поддерживает высокий уровень аксессибилити несмотря на ее малый размер. Заметьте, я не говорю, что маленькие формы это хорошая идея, наоборот я предпочитаю исключить их использование и стараюсь убедить в этом клиента (начальника, дизайнера), чтобы создать более качественный продукт удобный для пользователей.
Начнем с доступной разметки
Чтобы поддерживать хороший уровень аксессибилити, каждый элемент формы должен иметь связанный с ним элемент label определяющий назначение соответствующего элемента. Несмотря на компактность у нашей формы нет никаких причин не иметь таких указателей. Как и при создании страницы, мы начнем с функциональной и доступной разметки, а потом используем CSS и JavaScript, чтобы получить необходимое оформление и функциональность.
<form name="login" action="#" method="post">
<div id="username">
<label for="username-field" class="overlabel">
Username
</label>
<input id="username-field" type="text" name="username"
title="Username" value="" tabindex="1" />
</div>
<div id="password">
<label for="password-field" class="overlabel">
Password
</label>
<input id="password-field" type="password" name="password"
title="Password" value="" tabindex="2" />
</div>
<div id="submit">
<input type="submit" name="submit" value="Login"
tabindex="3" />
</div>
</form>
Элементы label и input попарно заключены в контейнер div, чтобы разметка была удобной при просмотре с отключенным CSS, конечно, можно использовать элемент fieldset, но мне кажется одно поле это еще не набор полей.
Элементам label мы задаем класс overlabel, не только для того чтобы использовать его как сектор CSS, но и чтобы, выбрав с помощью JavaScript все элементы с таким классом, установить для них обработчики событий. Использование классов вместо идентификаторов позволяет нам легко работать с набором элементов без необходимости знать конкретные идентификаторы.
В дополнение к элементам label, для каждого input мы установим атрибут title, это позволит пользователям видеть название поля даже после того как элемент label будет скрыт.
Потом добавим стили.
form#login {
position:relative;
}
div#username,
div#password {
position:relative;
float:left;
margin-right:3px;
}
input#username-field,
input#password-field {
width:10em;
}
label.overlabel {
position:absolute;
top:3px;
left:5px;
z-index:1;
color:#999;
}
В этих стилях нет ничего удивительного, используется абсолютное позиционирование и z-index, чтобы поместить элементы label на передний план и float, чтобы выстроить элементы input в одну линию.
В зависимости от размеров шрифта и поля ввода, нужно откорректировать положение label, но этот пример хорошо отображается во всех современных браузерах. В нем используются масштабируемые единицы (em) чтобы дать пользователям возможность менять размер шрифта и быть уверенными что метки и поля увеличиваются пропорционально.
Скрипт
Элемент label должен быть скрыт, если выбрано соответствующее ему поле, и оставаться в таком положении, если в поле введены данные, это могут быть случаи, когда при загрузке страницы задается значение атрибута value или когда пользователь вводит значение после загрузки страницы.
Поскольку для скрытия label используется JavaScript нужно немного изменить стили, чтобы метки оставались видимыми, если JavaScript недоступен.
label.overlabel {
color:#999;
}
label.overlabel-apply {
position:absolute;
top:3px;
left:5px;
z-index:1;
color:#999;
}
В целях обеспечения хорошего уровня аксессибилити используем для скрытия текста отрицательное значение text-indent вместо display:none.
function initOverLabels () {
if (!document.getElementById) return;
var labels, id, field;
// устанавливаем обработчики onfocus и onblur
// чтобы скрывать и отображать метки
// с именем класса 'overlabel'
labels = document.getElementsByTagName('label');
for (var i = 0; i < labels.length; i++) {
if (labels[i].className == 'overlabel') {
// пропускаем метки которые не связаны
// с полем ввода
id = labels[i].htmlFor || labels[i].getAttribute('for');
if (!id || !(field = document.getElementById(id))) {
continue;
}
// изменяем класс метки
// чтобы расположить ее перед полем ввода.
labels[i].className = 'overlabel-apply';
// скрываем метку если поле
// имеет значение по умолчанию
if (field.value !== '') {
hideLabel(field.getAttribute('id'), true);
}
// устанавливаем обработчики
// скрывающие и отображающие метки
field.onfocus = function () {
hideLabel(this.getAttribute('id'), true);
};
field.onblur = function () {
if (this.value === '') {
hideLabel(this.getAttribute('id'), false);
}
};
// обрабатываем клики на метки (для Safari).
labels[i].onclick = function () {
var id, field;
id = this.getAttribute('for');
if (id && (field = document.getElementById(id))) {
field.focus();
}
};
}
}
};
function hideLabel (field_id, hide) {
var field_for;
var labels = document.getElementsByTagName('label');
for (var i = 0; i < labels.length; i++) {
field_for = labels[i].htmlFor
|| labels[i].getAttribute('for');
if (field_for == field_id) {
labels[i].style.textIndent = (hide) ? '-1000px' : '0px';
return true;
}
}
}
window.onload = function () {
setTimeout(initOverLabels, 50);
};
Скрипт перебирает все элементы label на странице в поисках тех имя класса которых overlabel, определяет связан ли данный label с каким либо элементом input при помощи атрибута for, таким label присваивается класс overlabel-apply и устанавливаются обработчики событий onfocus и onblur для соответствующего элемента input.
Раньше я уже упоминал, что этот скрипт работает независимо от атрибутов name и id элементов input, он находит элемента label с именем класса overlabel, потому что только классы позволяют задать стили для множества элементов на странице, просто здесь они используются еще и для того чтобы придать этим элементам дополнительную функциональность. Снимаю шляпу перед Daniel Nolan, который использовал этот метод в его Image Rollover code еще несколько лет назад.
Может показаться странным, что обработчик onload использует небольшую задержку, это сделано, для совместимости с браузерами, сохраняющими данные формы, чтобы пользователю не приходилось вводить их повторно. Такие браузеры часто заполняют форму сохраненными значениями, после того как страница полностью загружена, а небольшая пауза позволяет сделать так чтобы label не закрывали эту информацию.
Заметьте, что только в последней части функции initOverLabels используется код специфический для конкретного браузера. Клик на элементе label обычно передает фокус соответствующему элементу input, но в Safari это не так и label не позволяют пользователю воспользоваться полями для ввода данных. Чтобы преодолеть эту проблему мы добавим элементам label обработчик onclick который передает фокус связанному элементу input.
Итог
Работающий пример использует чистую и доступную технику для размещения имени поля в самом поле без использования атрибута value. Подтверждение формы без ввода соответствующих значений передает серверному скрипту пустые поля вместо «Логин» и «Пароль». Кроме того, мы можем быстро добавить эту технику к любой форме, изменив имя класса элементов label и добавив немного CSS и JavaScript.
Как бонус, если пользователь заходит на сайт с отключенным JavaScript или CSS, форма остается удобной, а метки отражают значение каждого поля, как и должны.
Translated with the permission of A List Apart Magazine and the author[s].

















andruha 30 мая, 2007 12:08 #
Взял на заметку. Спасибо.
kost 31 мая, 2007 11:49 #
Спасибо за перевод.
Filosof 31 мая, 2007 19:59 #
Дяка
Дякую за статтю. Дуже цінна ;-)
5e-1 2 июня, 2007 15:30 #
В стилях css ошибка. Неправильно описаны стили для label. Не
div.overlabel label, а просто.overlabel. И на странице примера тоже поправьте.Евгений 2 июня, 2007 19:01 #
Нимогу такого найти, пример работает отлично, проверьте, пожалуйста еще раз.
5e-1 4 июня, 2007 11:59 #
Вот здесь вот исправьте
Евгений 4 июня, 2007 12:08 #
Спасибо, исправил.
Не думал, что из примера с ошибкой можно сделать работающий и проверял только последний пример.
AnnDi 17 июня, 2007 4:12 #
Спасибо за статью, очень полезна!
Emm 27 июня, 2007 18:58 #
Неприятность
Прочитал статью, интересная примочка, но при загрузке страницы, виден неприятный скачок, т.е сначала показывается надписи сбоку а потом через мгновение над полем как и должно быть при включенном js. Вот тут появился вопрос, а нельзя ли как-то устранить это блик?
Евгений 28 июня, 2007 14:55 #
Вчера вечером придумал как решить эту проблему и запостил коммент на локальную версию сайта, чтобы утром еще раз подумать над ним, а утром обновил базу :(
Вот пример, в котором нет скачка при загрузке (по крайней мере в тех браузерах которыми я располагаю).
Нужно запускать скрипт сразу же после загрузки формы с помощью строки
<script type="text/javascript">initOverLabels();</script>(не дожидаясьonload, потому что браузеры начинают рендерить страницу до окончания загрузки), для этого его нужно загрузить раньше формы (придется размещать скрипт в html файле, потому что с внешним файлом скриптов этого нельзя гарантировать).Но если запускать скрит слишком рано в некоторых браузерах
labelбудет перекрывать запомненные значения полей, чтобы избежать этого нужно немного модифицировать функциюinitOverLabels, для того чтобы она скрывалаlabel, если поле не пустое, и вызвать ее по событиюonload(а для некоторых браузеров и еще раз с некоторой задержкой).Вот часть функции в которую внесены изменения.
if (labels[i].className == 'overlabel' || labels[i].className == 'overlabel-apply')Growlin 23 июля, 2007 9:53 #
Аксессибилити - может быть, все-таки лучше писать "доступность".
Евгений 23 июля, 2007 14:59 #
Иногда так и перевожу, но термин достаточно устоявшийся и известный, не вижу смысла его избегать.