Самый понятный учебник веб-разработки для получения пользовательских вводных
Элементы HTML-форм позволяют собирать данные от посетителей вашего сайта. Списки рассылки, контактные формы и комментарии в блогах - обычные примеры для небольших сайтов, но в организациях, которые полагаются на свой сайт для получения прибыли, формы священны и почитаемы.
Формы - это "денежные страницы". С их помощью сайты электронной коммерции продают свои товары, SaaS-компании* собирают оплату за свои услуги, а некоммерческие группы собирают деньги в Интернете. Многие компании оценивают успех своего сайта по эффективности его форм, потому что они отвечают на такие вопросы, как "сколько лидов наш сайт отправил в отдел продаж?" и "сколько людей подписались на наш продукт на прошлой неделе?". Зачастую формы подвергаются бесконечным A/B-тестированиям и оптимизации.
* - SaaS (Software as a service) - программное обеспечение как услуга.
У функциональной HTML-формы есть два аспекта: фронтенд (пользовательский интерфейс) и бэкенд (внутренний сервер). Первый - это внешний вид формы (определяемый HTML и CSS), а второй - это код, который ее обрабатывает (сохраняет данные в БД, отправляет e-mail и т. д.). Ниже мы полностью сосредоточимся на фронтенде. Обработку форм (бэкенд) оставим для следующего учебника.
К сожалению, невозможно обойти тот факт, что стилизация форм - это сложно. Всегда полезно иметь макет страницы, которую вы хотите создать, прежде чем начать ее кодировать, и это особенно актуально для форм. Итак, вот пример, который мы будем создавать в этой главе:
Как видите, это форма подачи заявок на участие в конференции для фиктивного докладчика. В ней присутствует довольно хороший набор элементов HTML-форм: различные типы текстовых полей, группа радиокнопок, выпадающее меню, флажок и кнопка отправки.
Создайте новый Atom-проект под названием forms и поместите в него новый HTML-файл под названием speaker-submission.html. Для начала добавим разметку для заголовка. (Эй, между прочим, здесь тоже есть семантический HTML!)
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'/>
<title>Speaker Submission</title>
<link rel='stylesheet' href='styles.css'>
</head>
<body>
<header class='speaker-form-header'>
<h1>Speaker Submission</h1>
<p><em>Want to speak at our fake conference? Fill out
this form.</em></p>
</header>
</body>
</html>
Затем создайте файл styles.css и добавьте в него следующий CSS. Здесь используется простая техника flexbox для центрирования заголовка (и формы) независимо от ширины окна браузера:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
color: #5D6063;
background-color: #EAEDF0;
font-family: "Helvetica", "Arial", sans-serif;
font-size: 16px;
line-height: 1.3;
display: flex;
flex-direction: column;
align-items: center;
}
.speaker-form-header {
text-align: center;
background-color: #F6F7F8;
border: 1px solid #D6D9DC;
border-radius: 3px;
width: 80%;
margin: 40px 0;
padding: 50px;
}
.speaker-form-header h1 {
font-size: 30px;
margin-bottom: 20px;
}
Обратите внимание, что мы придерживаемся подхода mobile-first development (см. урок Адаптивный дизайн). Эти базовые правила CSS дают нам мобильный макет и служат основой макета для ноутбуков/ПК. Медиавыражение макета с фиксированной шириной (для ноутбуков/ПК) мы создадим чуть ниже.
Переходим к формам! Каждую HTML-форму открывает элемент с подходящим названием - <form>. Он поддерживает множество атрибутов, но самые важные из них - action и method. Добавим пустую форму в наш HTML-документ, прямо под <header>:
<form action='' method='get' class='speaker-form'>
</form>
Атрибут action определяет URL, который обрабатывает форму. Именно сюда отправляются данные, собранные формой, когда пользователь нажимает кнопку Submit. Обычно это особый URL, определенный вашим веб-сервером, который знает, как обрабатывать данные. Распространенные технологии бэкенда для обработки форм это Node.js, PHP и Ruby on Rails, но на этом уроке мы сосредоточимся на фронтенде.
Атрибут method может быть либо post, либо get. Оба они определяют, как форма отправляется на внутренний сервер. Это во многом зависит от того, как ваш веб-сервер хочет обрабатывать форму, но общее правило гласит: используйте post, когда вы изменяете данные на сервере. А get оставьте для тех случаев, когда вы только получаете данные.
Оставляя атрибут action пустым, мы указываем форме, что она должна отправляться по тому же URL. В сочетании с методом get это позволит нам просмотреть содержимое формы.
Конечно, сейчас перед нами пустая форма, но это не значит, что мы не можем добавить к ней несколько стилей, как к контейнеру <div>. Это превратит ее в поле, соответствующее нашему элементу <header>:
.speaker-form {
background-color: #F6F7F8;
border: 1px solid #D6D9DC;
border-radius: 3px;
width: 80%;
padding: 50px;
margin: 0 0 40px 0;
}
Для сбора пользовательских данных понадобится новый инструмент: элемент <input/>. Для создания текстового поля добавим в <form> немного кода:
<div class='form-row'>
<label for='full-name'>Name</label>
<input id='full-name' name='full-name' type='text'/>
</div>
Во-первых, наш контейнер <div> поможет со стилизацией. Это обычная практика для разделения элементов ввода. Во-вторых, для меток* формы у нас есть <label>. Это еще один семантический элемент HTML, так же как и <article> или <figcaption>. Атрибут метки for, должен совпадать с атрибутом id связанного с ней элемента <input/>.
* - метка - label.
В-третьих, элемент <input/> создает текстовое поле. Он немного отличается от других элементов, с которыми мы сталкивались, поскольку может кардинально менять внешний вид в зависимости от атрибута type, но он всегда создает некое интерактивное поле для ввода данных пользователем. Помимо текстовых данных далее мы рассмотрим и другие значения. Помните, что ID-селекторы - это плохо: атрибут id здесь нужен только для связывания его с элементом <label>.
Теоретически элемент <input/> представляет собой "переменную", отправляемую на внутренний сервер. Атрибут name определяет имя этой переменной, а значением является то, что пользователь ввел в текстовое поле. Заметьте, что можно заранее заполнить это значение, добавив к элементу <input/> атрибут value.
Элемент <input/> можно стилизовать, как и любой другой элемент HTML. Давайте добавим немного CSS в styles.css, чтобы немного украсить его. Мы используем все концепции из уроков Привет, CSS, Блоковая модель в CSS, Селекторы CSS и Flexbox:
.form-row {
margin-bottom: 40px;
display: flex;
justify-content: flex-start;
flex-direction: column;
flex-wrap: wrap;
}
.form-row input[type='text'] {
background-color: #FFFFFF;
border: 1px solid #D6D9DC;
border-radius: 3px;
width: 100%;
padding: 7px;
font-size: 14px;
}
.form-row label {
margin-bottom: 15px;
}
Новый тип CSS-селектора input[type='text'], называемый "селектором атрибутов", подбирает только те элементы <input/>, у которых атрибут type равен text. Это позволяет нам специально выбирать текстовые поля, а не радиокнопки, которые определяются одним и тем же HTML-элементом (<input type='radio'/>). Подробнее о селекторах атрибутов вы можете прочитать на сайте Mozilla Developer Network.
Все наши стили "распределены по именам" в селекторе потомка .form-row. Подобная разделенность стилей <input/> и <label> облегчает создание различных типов форм. Почему следует избегать глобальных селекторов input[type='text'] и label, мы узнаем, когда доберемся до радиокнопок.
Наконец, давайте изменим базовые стили, чтобы создать версию для ноутбуков/ПК. Добавьте в конец нашей таблицы стилей следующее медиавыражение.
@media only screen and (min-width: 700px) {
.speaker-form-header,
.speaker-form {
width: 600 px;
}
.form-row {
flex-direction: row;
align-items: flex-start; /* Чтоб избежать расползания */
margin-bottom: 20px;
}
.form-row input [type='text'] {
width: 250px;
height: initial;
}
.form-row label {
text-align: right;
width: 120px;
margin-top: 7px;
padding-right: 20px;
}
}
Посмотрите, как замечательно используется свойство flex-direction, чтобы <label> отображалась поверх своего элемента <input/> в мобильном макете, но слева от него в макете для ноутбуков/ПК.
Атрибут type элемента <input/> также позволяет выполнять базовую валидацию ввода. Добавим еще один элемент ввода, принимающий только адреса электронной почты, а не любые текстовые значения:
<div class='form-row'>
<label for='email'>Email</label>
<input id='email'
name='email'
type='email'
placeholder='[email protected]'/>
</div>
Это работает точно так же, как ввод type='text', за исключением автоматической проверки ввода email-адреса. Попробуйте в Firefox набрать что-то не являющееся мейл-адресом, а затем щелкните за пределами поля, чтобы оно потеряло фокус и подтвердило ввод. Поле станет красным, показывая, что введенное значение неверно. Chrome и Safari не выполняют такую проверку, до тех пор пока пользователь не попытается отправить форму. Мы увидим это в действии чуть ниже.
Однако это больше, чем просто валидация. Сообщая браузерам, что мы ищем адрес электронной почты, они могут обеспечить более интуитивный пользовательский опыт. Например, когда браузер смартфона видит атрибут type='email', он отображает особую клавиатуру с легкодоступным символом @.
Также обратите внимание на атрибут placeholder, отображающий какой-либо стандартный текст, когда элемент <input/> пуст. Это хороший прием UX*, побуждающий пользователя ввести собственное значение.
Помимо email-адресов, существует множество других встроенных параметров валидации. Подробнее см. MDN- справочник по <input/>. Особенно интересны атрибуты required, minlength, maxlength и pattern.
* - UX - User Experience - Опыт взаимодействия.
Мы хотим, чтобы наше поле email соответствовало текстовому полю из предыдущей секции. Поэтому добавим еще один селектор атрибутов к существующему правилу input[type='text'], как показано ниже:
/* Измените это правило */
.form-row input[type='text'] {
background-color: #FFFFFF;
/* ... */
}
/* Чтобы получить еще один селектор */
.form-row input[type='text'],
.form-row input[type='email'] {
background-color: #FFFFFF;
/* ... */
}
Опять же, мы не хотим использовать здесь обычный input type селектор, потому что тогда будут стилизованы все элементы <input/>, включая радиокнопки и чекбоксы. Это часть того, что делает стилизацию форм сложной. Понимание CSS для выделения именно тех элементов, которые вам нужны - крайне важный навык.
Не будем забывать и о стилях для ноутбука/ПК. В нашем медиавыражении обновите правило input[type='text'] так, чтобы оно соответствовало следующему (обратите внимание, что мы готовимся к следующим секциям с селекторами select и textarea):
@media only screen and (min-width: 700px) {
/* ... */
.form-row input [type='text'],
.form-row input [type='text'], /* Добавьте */
.form-row input [type='text'], /* эти */
.form-row input [type='text'], /* селекторы */
width: 250px;
height: initial;
}
/* ... */
}
Поскольку теперь у нас возможно "верное" и "неверное" входное значение, наверно следует донести это до пользователей. Псевдоклассы :invalid и :valid позволяют стилизовать эти состояния независимо друг от друга. Например, мы хотим сделать рамку и текст красными, если пользователь ввел неприемлемое значение. Добавьте следующее правило в нашу таблицу стилей, вне медиавыражения:
.form-row input[type='text']: invalid,
.form-row input[type='email']: invalid {
border: 1px solid #D55C5F;
color: #D55C5F;
box-shadow: none;
/* Удаление стандартного красного свечения в Firefox */
}
Пока мы не добавим кнопку отправки, вы сможете увидеть это только в Firefox, но идея вам понятна. Существует еще псевдокласс :focus, выбирающий элемент, который пользователь заполняет в данный момент. Это дает еще больше контроля над внешним видом форм.
Изменение свойства type элемента <input/> на radio превращает его в радиокнопку. С радиокнопками дело обстоит немного сложнее, чем с текстовыми полями, поскольку они всегда работают в группах, позволяя пользователю выбрать один вариант из множества предопределенных.
Это означает, что нужна не только метка для каждого элемента <input/>, но и способ группировки радиокнопок и маркировки всей группы. Для этого предназначены <fieldset> и <legend>. Каждая созданная группа радиокнопок должна:
Наш пример с радиокнопкой содержит все эти компоненты. Добавьте в элемент <form> под полем электронной почты следующее:
<fieldset class='legacy-form-row'>
<legend>Type of Talk</legend>
<input id='talk-type-1'
name='talk-type'
type='radio'
value='main-stage' />
<label for='talk-type-1' class='radio-label'>Main Stage</label>
<input id='talk-type-2'
name='talk-type'
type='radio'
value='workshop'
checked />
<label for='talk-type-2' class='radio-label'>Workshop</label>
</fieldset>
В отличие от текстовых полей, пользователь не может вводить свои значения в радиокнопки, поэтому каждая из них нуждается в явном атрибуте value. Именно это значение отправится на сервер, когда пользователь отправит форму. Также очень важно, чтобы у каждой радиокнопки был одинаковый атрибут name, иначе форма не поймет, что они входят в одну группу.
Мы также добавили новый атрибут - checked. Это атрибут "булева типа". Он никогда не принимает значения - он либо существует в элементе <input/>, либо не существует. Если он присутствует на элементе радиокнопки или галочки, то этот элемент будет выбран/отмечен по умолчанию.
Когда речь идет о стилизации радиокнопок, против нас работает несколько вещей. Во-первых, просто больше элементов, о которых нужно беспокоиться. Во-вторых, элементы <fieldset> и <legend> имеют довольно уродливые стили по умолчанию, и в разных браузерах они не слишком согласованы. В-третьих, на момент написания этой статьи <fieldset> не поддерживает flexbox.
Не волнуйтесь! Это хороший пример того, как обтекание [floats] может быть хорошим Plan B для устаревших/неудобных элементов. Вы заметили, что вместо существующего класса радиокнопки .form-row, мы выбрали новый класс .legacy-form-row? Так он будет полностью отделен от других элементов, используя floats вместо flexbox.
Начнем со стилей для смартфонов и планшетов, добавив следующие правила за пределами медиавыражения. Мы избавимся от стандартных стилей <fieldset> и <legend>, затем сделаем радиокнопки и ярлыки обтекаемыми, чтобы они отображались в одну строку под <legend>:
.legacy-form-row {
border: none;
margin-bottom: 40px;
}
.legacy-form-row legend {
margin-bottom: 15px;
}
.legacy-form-row .radio-label {
display: block;
font-size: 14px;
padding: 0 20px 0 10px;
}
.legacy-form-row input [type='radio'] {
margin-top: 2px;
}
.legacy-form-row .radio-label,
.legacy-form-row input [type='radio'] {
float: left;
}
В версии для ноутбуков/ПК нужно, чтобы <legend> располагался на одной линии с элементами <label> из предыдущей секции (отсюда ширина: 120px), чтобы все элементы были плавающими (float), и отображались на одной линии. Обновите наши медиавыражения, включив в них следующее:
@media only screen and (min-width: 700px) {
/* ... */
.legacy-form-row {
margin-bottom: 10px;
}
.legacy-form-row legend {
width: 120px;
text-align: right;
padding-right: 20px;
}
.form-row legend {
float: left;
}
}
Что касается макетов, то это довольно хорошее кроссбраузерное решение. Однако настройка внешнего вида самой кнопки - это уже другая история. Это возможно, если воспользоваться атрибутом checked, но это немного сложно. Мы оставим вас гуглить "custom radio button CSS" и исследовать эту кроличью нору самостоятельно.
Выпадающие меню являются альтернативой радиокнопкам, поскольку они позволяют пользователю выбрать один из множества вариантов. Элемент <select> представляет собой выпадающее меню и содержит множество элементов <option>, которые представляют каждый элемент.
<div class='form-row'>
<label for='t-shirt'>T-Shirt Size</label>
<select id='t-shirt' name='t-shirt'>
<option value='xs'>Extra Small</option>
<option value='s'>Small</option>
<option value='m'>Medium</option>
<option value='l'>Large</option>
</select>
</div>
Как и в элементах радиокнопки <input/>, у нас есть атрибуты name и value, которые передаются на внутренний сервер. Но вместо того, чтобы быть определенными в одном элементе, они распределены между элементами <select> и <option>.
Как и радиокнопки, элементы <select>, с трудом поддаются стилизации. На это есть своя причина. Выпадающие элементы - это сложный интерактивный элемент, и их поведение значительно отличается на разных устройствах. Например, на iPhone нажатие на элемент <select> приводит к появлению собственного прокручивающегося компонента пользовательского интерфейса, который значительно упрощает навигацию по меню.
Лучше всего позволить браузеру/устройству определить оптимальный способ предварительной настройки элемента <select>, поэтому мы сохраним простоту нашего CSS. К сожалению, даже самые простые вещи оказываются иногда на удивление сложными. Например, попробуйте изменить размер шрифта нашего элемента <select>:
.form-row select {
width: 100%;
padding: 5px;
font-size: 14px; /* Это не работает в Chrome или Safari */
}
Это работает в Firefox, но не в Chrome или Safari! Чтобы исправить ситуацию, можно использовать префикс для свойства appearance, характерный для конкретного производителя:
.form-row select {
width: 100%;
padding: 5px;
font-size: 14px; /* Это не работает в Chrome или Safari */
-webkit-appearance: none; /* Данный префикс заставит это работать */
}
Префикс -webkit будет применяться только к Chrome и Safari (они работают на движке WebKit), а Firefox останется незатронутым. По сути это хак. Даже MDN говорит о том, что не стоит использовать это свойство CSS.
Подобные трудности со стилями - серьезный аспект при создании формы. Если вам нужны пользовательские стили, лучше использовать радиокнопки или виджеты JavaScript UI. Bootstrap Dropdowns и jQuery Selectmenu - распространенные JavaScript-решения для настройки меню выбора. В любом случае, теперь вы хотя бы понимаете суть проблемы. Подробнее о проблемах <select> можно прочитать здесь.
Элемент <textarea> создает многострочное текстовое поле, предназначенное для большого количества текста от пользователя. Они подходят для таких вещей, как биографии, эссе и комментарии. Давайте добавим <textarea> в нашу форму вместе с небольшим инструктирующим текстом:
<div class='form-row'>
<label for='abstract'>Abstract</label>
<textarea id='abstract' name='abstract'></textarea>
<div class='instructions'>Describe your talk in 500 words or less</div>
</div>
Заметьте, что этот элемент не является самозакрывающимся, как элемент <input/>, поэтому вам всегда нужен закрывающий тег </textarea>. Если вы хотите добавить текст по умолчанию, он должен находиться внутри тега, а не в атрибуте value.
К счастью, стилизация текстовых полей довольно проста. Добавьте в ваш файл styles.css (перед медиавыражением!) следующее:
.form-row textarea {
font-family: "Helvetica", "Arial", sans-serif;
font-size: 14px;
border: 1px solid #D6D9DC;
border-radius: 3px;
min-height: 200px;
margin-bottom: 10px;
padding: 7px;
resize: none;
}
.form-row .instructions {
color: #999999;
font-size: 14px;
margin-bottom: 30px;
}
По умолчанию многие браузеры позволяют пользователю изменять размер элементов <textarea> до нужных размеров. Мы отключили это с помощью свойства resize.
Также нам нужно немного поправить макет для ноутбуков/ПК. <div>-элемент .instructions должен находиться под <textarea>, поэтому давайте сдвинем его влево на ширину колонки <label>. В конец нашего медиавыражения добавьте следующее правило:
@media only screen and (min-width: 700px) {
/* ... */
.form-row .instructions {
margin-left: 120px;
}
}
Чекбоксы - это что-то вроде радиокнопок, но они позволяют пользователю выбрать сколько угодно вариантов вместо всего одного. Это упрощает задачу, ибо браузеру не нужно знать, какие флажки входят в одну группу. Другими словами, нам не нужна обертка <fieldset> или общие атрибуты имен [shared name attributes]. Добавьте в конец формы следующее:
<div class='form-row'>
<label class='checkbox-label' for='available'>
<input id='available'
name='available'
type='checkbox'
value='is-available'/>
<span>I’m actually available the date of the talk</span>
</label>
</div>
То, как мы использовали <label> здесь, немного отличается от предыдущих секций. Вместо того чтобы быть отдельным элементом, <label> обертывает соответствующий элемент <input/>. Это вполне допустимо, и так будет проще подобрать нужный нам макет. Но все же лучше использовать атрибут for.
Для мобильного макета нам нужно всего лишь переопределить margin-bottom, который мы установили для остальных элементов <label>. Добавьте в styles.css, вне медиавыражения, следующее:
.form-row .checkbox-label {
margin-bottom: 0;
}
А внутри медиавыражения мы должны учесть 120-пиксельную колонку с метками:
@media only screen and (min-width: 700px) {
/* ... */
.form-row .checkbox-label {
margin-left: 120px;
width: auto;
}
}
Обернув и чекбокс, и текст метки, можно использовать ширину auto для расположения всего поля формы в одну строку. Помните, что ширина auto заставляет блок соответствовать размеру его контента?
И наконец, завершим нашу форму кнопкой отправки. Элемент <button> это кнопка, отправляющая содержащуюся в ней <form>:
<div class='form-row'>
<button>Submit</button>
</div>
При нажатии на кнопку браузер проверяет все элементы <input/> в форме. Если проблем с валидацией не возникло, он отправляет ее по URL-адресу action. Если в поле email вы ввели что-то, не являющееся адресом электронной почты и нажмете кнопку <button>, то увидите сообщение об ошибке.
Это также дает нам возможность увидеть, как данные пользователя отправляется на сервер. Сначала заполните все поля <input/> и убедитесь, что e-mail валидируется верно. Затем нажмите кнопку и просмотрите полученный URL в браузере. Там должно быть что-то вроде этого:
speaker-submission.html?full-name=Rick&email=rick%40internetingishard.com&talk-type=workshop&t-shirt=l&abstract=Derp.&available=is-available
Все, что находится после ? представляет собой переменные в нашей форме. За атрибутом name каждого <input/> следует знак равенства, затем его значение. Каждая переменная отделяется символом &. Если бы у нас был внутренний сервер, ему было бы очень просто получить всю эту информацию, запросить базу данных (или что-то еще) и сообщить, была ли отправка формы успешной или нет.
В секции "Псевдоклассы" урока Селекторы CSS у нас был опыт стилизации кнопок. Тогда мы применяли эти стили к элементу <a>, но мы можем использовать те же приемы и для <button>.
Очистим этот безобразный стандартный стиль <button>, добавив в таблицу стилей следующее:
.form-row button {
font-size: 16px;
font-weight: bold;
color: #FFFFFF;
background-color: #5995DA;
border: none;
border-radius: 3px;
padding: 10px 40px;
cursor: pointer;
}
.form-row button:hover {
background-color: #76AEED;
}
.form-row button:active {
background-color: #407FC7;
}
Как и в случае с чекбоксом, учитываем колонку с метками шириной 120px, поэтому включаем в медиавыражения еще одно правило:
@media only screen and (min-width: 700px) {
/* ... */
.form-row button{
margin-left: 120px;
}
}
На этом уроке вы узнали про наиболее распространенные элементы форм HTML. Теперь у вас есть все эти инструменты для сбора данных от посетителей вашего сайта:
Для создания красивых форм вы должны хорошо разбираться в HTML и CSS, но для того, чтобы сделать эти формы функциональными, требуются навыки, которых у вас пока нет. Эти навыки выходят за рамки данного учебника, но, возможно, вам будет полезно узнать о них. В общем, существует два способа обработки форм:
В зависимости от структуры вашей компании, обработка форм может не входить в ваши обязанности фронтенд-веб-разработчика. В этом случае скооперируйтесь с бэкенд-разработчиком из вашей команды, чтобы убедиться, что <form> отправляет правильные пары имя-значение. Иначе вам придется позаботиться о том, чтобы фронтенд и бэкенд ваших форм гармонично сочетались друг с другом.
Далее вас ожидает заключительный урок. Мы завершим навыки работы с фронтендом, подробно обсудив веб-шрифты и практические принципы типографики, которые должен знать каждый разработчик.