Стили HTML форм - Изучение веб-разработки

Стили HTML форм
===============

* Назад  (en-US)
* Обзор: Forms
* Далее  (en-US)

В этой статье Вы узнает, как использовать CSS с HTML-формами, чтобы сделать их (надеюсь) более красивыми. Удивительно, но это может быть немного сложнее. По историческим и техническим причинам виджеты форм плохо сочетаются с CSS. Из-за этих трудностей многие разработчики предпочитают создавать свои собственные HTML-виджеты, чтобы получить контроль над своим внешним видом. Однако в современных браузерах веб-дизайнеры все больше контролируют дизайн элементов формы. Давайте приступим!

Почему так сложно стилизовать виджеты форм с помощью CSS?
---------------------------------------------------------

На заре Интернета, примерно в 1995 году, в HTML 2 были добавлены элементы управления формой. Из-за сложности виджетов форм разработчики решили полагаться на базовую операционную систему для управления ими и их рендеринга.

Несколько лет спустя был создан CSS, и то, что было технической необходимостью, то есть использование собственных виджетов для реализации элементов управления формой, стало требованием к стилю. В первые дни CSS, стилизация элементов управления формы не была приоритетом.

Поскольку пользователи привыкли к внешнему виду своих соответствующих платформ, поставщики браузеров неохотно делают элементы управления формами стилевыми; и по сей день все ещё чрезвычайно трудно перестроить все элементы управления, чтобы сделать их стилизованными.

Даже сегодня ни один браузер полностью не реализует CSS 2.1. Однако со временем поставщики браузеров улучшили свою поддержку CSS для элементов формы, и, несмотря на плохую репутацию в отношении удобства использования, теперь вы можете использовать CSS для стилизации HTML форм (en-US).

### Не все виджеты созданы равными, когда задействован CSS

В настоящее время некоторые трудности остаются при использовании CSS с формами. Эти проблемы можно разделить на три категории:

#### Хорошая

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

1. `<form>`
2. `<fieldset>`
3. `<label>`
4. `<output>`

Сюда также входят все виджеты текстового поля (как однострочные, так и многострочные) и кнопки.

#### Плохая

Некоторые элементы редко могут быть стилизованы, и могут потребовать некоторых сложных уловок, иногда требующих углублённых знаний CSS3.

Они включают в себя элемент `<legend>`, но его нельзя правильно расположить на всех платформах. Флажки и переключатели также не могут быть стилизованы напрямую, однако, благодаря CSS3 вы можете обойти это. Контент `placeholder` не может быть стилизован каким-либо стандартным способом, однако все браузеры, которые его реализуют, также реализуют собственные псевдо-элементы CSS или псевдоклассы, которые позволяют его стилизовать.

Мы опишем, как обрабатывать эти более конкретные случаи, в статье «Расширенные стили для HTML-форм». (en-US)

#### The ugly

Some elements simply can't be styled using CSS. These include: all advanced user interface widgets, such as range, color, or date controls; and all the dropdown widgets, including `<select>`, `<option>`, `<optgroup>` and `<datalist>` elements. The file picker widget is also known not to be stylable at all. The new `<progress>` and `<meter>` elements also fall in this category.

The main issue with all these widgets, comes from the fact that they have a very complex structure, and CSS is not currently expressive enough to style all the subtle parts of those widgets. If you want to customize those widgets, you have to rely on JavaScript to build a DOM tree you'll be able to style. We explore how to do this in the article How to build custom form widgets (en-US).

Basic styling
-------------

To style elements that are easy to style (en-US) with CSS, you shouldn't face any difficulties, since they mostly behave like any other HTML element. However, the user-agent style sheet of every browser can be a little inconsistent, so there are a few tricks that can help you style them in an easier way.

### Search fields

Search boxes are the only kind of text fields that can be a little tricky to style. On WebKit based browsers (Chrome, Safari, etc.), you'll have to tweak it with the `-webkit-appearance` proprietary property. We discuss this property further in the article: Advanced styling for HTML forms (en-US).

#### Example

```html
<form>
  <input type="search" />
</form>
```

```css
input[type="search"] {
  border: 1px dotted #999;
  border-radius: 0;

  -webkit-appearance: none;
}
```

![This is a screenshot of a search filed on Chrome, with and without the use of -webkit-appearance](/files/4153/search-chrome-macos.png)

As you can see on this screenshot of the search field on Chrome, the two fields have a border set as in our example. The first field is rendered without using the `-webkit-appearance` property, whereas the second is rendered using `-webkit-appearance:none`. This difference is noticeable.

### Fonts and text

CSS font and text features can be used easily with any widget (and yes, you can use `@font-face` with form widgets). However, browsers' behaviors are often inconsistent. By default, some widgets do not inherit `font-family` and `font-size` from their parents. Many browsers use the system default appearance instead. To make your forms' appearance consistent with the rest of your content, you can add the following rules to your stylesheet:

```css
button,
input,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
}
```

The screenshot below shows the difference; on the left is the default rendering of the element in Firefox on Mac OS X, with the platform's default font style in use. On the right are the same elements, with our font harmonization style rules applied.

![This is a screenshot of the main form widgets on Firefox on Mac OSX, with and without font harmonization](/files/4157/font-firefox-macos.png)

There's a lot of debate as to whether forms look better using the system default styles, or customized styles designed to match your content. This decision is yours to make, as the designer of your site, or Web application.

### Box model

All text fields have complete support for every property related to the CSS box model (`width`, `height`, `padding`, `margin`, and `border`). As before, however, browsers rely on the system default styles when displaying these widgets. It's up to you to define how you wish to blend them into your content. If you want to keep the native look and feel of the widgets, you'll face a little difficulty if you want to give them a consistent size.

**This is because each widget has their own rules for border, padding and margin.** So if you want to give the same size to several different widgets, you have to use the `box-sizing` property:

```css
input,
textarea,
select,
button {
  width: 150px;
  margin: 0;

  -webkit-box-sizing: border-box; /\* For legacy WebKit based browsers \*/
  -moz-box-sizing: border-box; /\* For legacy (Firefox <29) Gecko based browsers \*/
  box-sizing: border-box;
}
```

![This is a screenshot of the main form widgets on Chrome on Windows 7, with and without the use of box-sizing.](/files/4161/size-chrome-win7.png)

In the screenshot above, the left column is built without `box-sizing`, while the right column uses this property with the value `border-box`. Notice how this lets us ensure that all of the elements occupy the same amount of space, despite the platform's default rules for each kind of widget.

### Positioning

Positioning of HTML form widgets is generally not a problem; however, there are two elements you should take special note of:

#### legend

The `<legend>` element is okay to style, except for positioning. In every browser, the `<legend>` element is positioned on top of the top border of its `<fieldset>` parent. There is absolutely no way to change it to be positioned within the HTML flow, away from the top border. You can, however, position it absolutely or relatively, using the `position` property. But otherwise it is part of the fieldset border.

Because the `<legend>` element is very important for accessibility reasons, it will be spoken by assistive technologies as part of the label of each form element inside the fieldset, it's quite often paired with a title, and then hidden in an accessible way. For example:

##### HTML

```html
<fieldset>
  <legend>Hi!</legend>
  <h1>Hello</h1>
</fieldset>
```

##### CSS

```css
legend {
  width: 1px;
  height: 1px;
  overflow: hidden;
}
```

#### textarea

By default, all browsers consider the `<textarea>` (en-US) element to be an inline block, aligned to the text bottom line. This is rarely what we actually want to see. To change from `inline-block` to `block`, it's pretty easy to use the `display` property. But if you want to use it inline, it's common to change the vertical alignment:

```css
textarea {
  vertical-align: top;
}
```

Example
-------

Let's look at a concrete example of how to style an HTML form. This will help make a lot of these ideas clearer. We will build the following "postcard" contact form:

![This is what we want to achieve with HTML and CSS](/files/4149/screenshot.png)

If you want to follow along with this example, make a local copy of our postcard-start.html file, and follow the below instructions.

### The HTML

The HTML is only slightly more involved than the example we used in the first article of this guide (en-US); it just has a few extra IDs and a title.

```html
<form>
  <h1>to: Mozilla</h1>

  <div id="from">
    <label for="name">from:</label>
    <input type="text" id="name" name="user\_name" />
  </div>

  <div id="reply">
    <label for="mail">reply:</label>
    <input type="email" id="mail" name="user\_email" />
  </div>

  <div id="message">
    <label for="msg">Your message:</label>
    <textarea id="msg" name="user\_message"></textarea>
  </div>

  <div class="button">
    <button type="submit">Send your message</button>
  </div>
</form>
```

Add the above code into the body of your HTML.

### Organizing your assets

This is where the fun begins! Before we start coding, we need three additional assets:

1. The postcard background — download this image and save it in the same directory as your working HTML file.
2. A typewriter font: The "Secret Typewriter" font from fontsquirrel.com — download the TTF file into the same directory as above.
3. A handdrawn font: The "Journal" font from fontsquirrel.com — download the TTF file into the same directory as above.

Your fonts need some more processing before you start:

1. Go to the fontsquirrel Webfont Generator.
2. Using the form, upload both your font files and generate a webfont kit. Download the kit to your computer.
3. Unzip the provided zip file.
4. Inside the unzipped contents you will find two `.woff` files and two `.woff2` files. Copy these four files into a directory called fonts, in the same directory as before. We are using two different files for each font to maximise browser compatibility; see our Web fonts article for a lot more information.
### The CSS

Now we can dig into the CSS for the example. Add all the code blocks shown below inside the `<style>` element, one after another.

First, we prepare the ground by defining our `@font-face` rules, all the basics on the `<body>` element, and the `<form>` element:

```css
@font-face {
  font-family: "handwriting";
  src:
    url("fonts/journal-webfont.woff2") format("woff2"),
    url("fonts/journal-webfont.woff") format("woff");
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: "typewriter";
  src:
    url("fonts/veteran\_typewriter-webfont.woff2") format("woff2"),
    url("fonts/veteran\_typewriter-webfont.woff") format("woff");
  font-weight: normal;
  font-style: normal;
}

body {
  font: 21px sans-serif;

  padding: 2em;
  margin: 0;

  background: #222;
}

form {
  position: relative;

  width: 740px;
  height: 498px;
  margin: 0 auto;

  background: #fff url(background.jpg);
}
```

Now we can position our elements, including the title and all the form elements:

```css
h1 {
  position: absolute;
  left: 415px;
  top: 185px;

  font:
    1em "typewriter",
    sans-serif;
}

#from {
  position: absolute;
  left: 398px;
  top: 235px;
}

#reply {
  position: absolute;
  left: 390px;
  top: 285px;
}

#message {
  position: absolute;
  left: 20px;
  top: 70px;
}
```

That's where we start working on the form elements themselves. First, let's ensure that the `<label>`s are given the right font:

```css
label {
  font:
    0.8em "typewriter",
    sans-serif;
}
```

The text fields require some common rules. Simply put, we remove their `borders` and `backgrounds`, and redefine their `padding` and `margin`:

```css
input,
textarea {
  font:
    0.9em/1.5em "handwriting",
    sans-serif;

  border: none;
  padding: 0 10px;
  margin: 0;
  width: 240px;

  background: none;
}
```

When one of these fields gains focus, we highlight them with a light grey, transparent, background. Note that it's important to add the `outline` property, in order to remove the default focus highlight added by some browsers:

```css
input:focus,
textarea:focus {
  background: rgba(0, 0, 0, 0.1);
  border-radius: 5px;
  outline: none;
}
```

Now that our text fields are complete, we need to adjust the display of the single and multiple line text fields to match, since they won't typically look the same using the defaults.

The single-line text field needs some tweaks to render nicely in Internet Explorer. Internet Explorer does not define the height of the fields based on the natural height of the font (which is the behavior of all other browsers). To fix this, we need to add an explicit height to the field, as follows:

```css
input {
  height: 2.5em; /\* for IE \*/
  vertical-align: middle; /\* This is optional but it makes legacy IEs look better \*/
}
```

`<textarea>` (en-US) elements default to being rendered as a block element. The two important things here are the `resize` (en-US) and `overflow` properties. Because our design is a fixed-size design, we will use the `resize` property to prevent users from resizing our multi-line text field. The `overflow` property is used to make the field render more consistently across browsers. Some browsers default to the value `auto`, while some default to the value `scroll`. In our case, it's better to be sure every one will use `auto`:

```css
textarea {
  display: block;

  padding: 10px;
  margin: 10px 0 0 -10px;
  width: 340px;
  height: 360px;

  resize: none;
  overflow: auto;
}
```

The `<button>` element is really convenient with CSS; you can do whatever you want, even using pseudo-elements (en-US):

```css
button {
  position: absolute;
  left: 440px;
  top: 360px;

  padding: 5px;

  font: bold 0.6em sans-serif;
  border: 2px solid #333;
  border-radius: 5px;
  background: none;

  cursor: pointer;

  -webkit-transform: rotate(-1.5deg);
  -moz-transform: rotate(-1.5deg);
  -ms-transform: rotate(-1.5deg);
  -o-transform: rotate(-1.5deg);
  transform: rotate(-1.5deg);
}

button:after {
  content: " >>>";
}

button:hover,
button:focus {
  outline: none;
  background: #000;
  color: #fff;
}
```

And voila!

**Примечание:** If your example does not work quite like you expected and you want to check it against our version, you can find it on GitHub — see it running live (also see the source code).

Conclusion
----------

As you can see, as long as we want to build forms with just text fields and buttons, it's easy to style them using CSS. If you want to know more of the little CSS tricks that can make your life easier when working with form widgets, take a look at the form part of the normalize.css project.

In the next article (en-US), we will see how to handle form widgets which fall in the "bad" and "ugly" categories.

* Назад  (en-US)
* Обзор: Forms
* Далее  (en-US)
In this module
--------------

* Your first HTML form (en-US)
* How to structure an HTML form
* The native form widgets (en-US)
* Sending form data (en-US)
* Form data validation (en-US)
* How to build custom form widgets
* Sending forms through JavaScript
* HTML forms in legacy browsers (en-US)
* Styling HTML forms
* Advanced styling for HTML forms (en-US)
* Property compatibility table for form widgets (en-US)


Как создавать пользовательские виджеты форм - Изучение веб-разработки

Как создавать пользовательские виджеты форм
===========================================

Существует много случаев, когда возможностей стандартных элементов управления HTML форм недостаточно. Если вы хотите придать лучший вид (en-US) каким-либо элементам управления (например, `<select>`) или вы хотите добавить особое поведение, то у вас нет другого выбора, кроме как создать собственные элементы управления.

В этой статье мы рассмотрим как создать собственный элемент управления. Для этого мы возьмём пример: переделка элемента `<select>` .

**Примечание:** Мы сфокусируемся на создании виджетов, а не на том чтобы сделать код универсальным и многоразовым; поэтому будут использоваться некоторый нетривиальный JavaScript-код и манипуляции DOM в неизвестном контексте, что выходит за рамки этой статьи.

Дизайн, структура и семантика
-----------------------------

В начале создания пользовательского виджета необходимо обрисовать что именно вы хотите. Это сэкономит драгоценное время. Особенно важно чётко определить все состояния вашего виджета. Чтобы это сделать, лучше начать с существующего виджета, состояния и реакции которого хорошо известны, так что вы сможете просто подражать им насколько это возможно.

В нашем примере мы будем переделывать элемент `<select>`. Вот такой результат мы хотим достичь:

![Три состояния выпадающего списка](/ru/docs/Learn/Forms/How_to_build_custom_form_controls/custom-select.png)

Этот скриншот показывает три основных состояния нашего виджета: нормальное состояние (слева); активное состояние (посередине) и развёрнутое состояние (справа).

С точки зрения реакций нужно чтобы наш виджет взаимодействовал как с мышью, так и с клавиатурой, так же как и стандартный виджет. Давайте сначала определим, как виджет приходит в каждое состояние:

Виджет в нормальном состоянии когда:

* страница загружается
* виджет был активным и пользователь кликает где-то вне виджета
* виджет был активным и пользователь перемещает фокус на другой виджет при помощи клавиатуры

**Примечание:** Перемещение фокуса по странице обычно осуществляется клавишей "tab", но не везде. Например в Safari циклический переход между ссылками на странице осуществляется по умолчанию комбинацией Option+Tab.

Виджет в активном состоянии когда:

* пользователь кликает на него
* пользователь нажимает клавишу Tab, и он получает фокус
* виджет был в развёрнутом состоянии и пользователь кликает на виджет.

Виджет в развёрнутом состоянии:

* виджет в любом другом состоянии и пользователь кликает на него

Теперь, когда мы знаем, как изменяются состояния, важно определить, как изменить значение виджета:

Значение изменяется когда:

* пользователь кликает на один-из-вариантов когда виджет в развёрнутом состоянии
* пользователь нажимает клавиши стрелка вверх или вниз когда виджет в активном состоянии

Наконец, давайте определим, как будут вести себя варианты виджета:

* когда виджет развернут, выбранный вариант подсвечен
* когда курсор мыши находится над вариантом, он подсвечен и ранее подсвеченный вариант возвращается в его обычное состояние

Для нашего примера остановимся на этом; но, если вы внимательный читатель, вы заметите, что некоторые реакции отсутствуют. Например, как вы думаете, что произойдёт если пользователь нажмёт клавишу "tab" когда виджет в развёрнутом состоянии? Ответом будет... ничего. OK, правильная реакция кажется очевидной, но поскольку она не определена в наших спецификациях, то очень легко пропустить реализацию этой реакции. Это особенно верно для командной работы, когда те, кто определяет какими должны быть реакции виджета сами не реализуют их.

Другой забавный пример: что произойдёт, если пользователь нажмёт клавишу вверх или вниз когда виджет находится в развёрнутом состоянии? Это немного сложнее. Если вы предположите, что активное и развёрнутое состояние полностью различны, то ответом снова будет "ничего не произойдёт" , потому что мы не определили никаких взаимодействий с клавиатурой в открытом состоянии. С другой стороны, если вы предположите, что активное и развёрнутое состояние немного похожи, значение может изменится, но выбранный вариант точно не будет соответственно подсвечен, опять же потому, что мы не определили никаких действий с клавиатуры над вариантами когда виджет находится в развёрнутом состоянии (мы определили только то, что произойдёт, когда виджет развернётся, но ничего более).

В нашем примере пропущенные спецификации очевидны, так что мы с ними справимся, но это может стать реальной проблемой для новых экзотических виджетов, когда никто не имеет ни малейшего представления о том как они должны реагировать. Всегда лучше потратить время на этом этапе дизайна, потому что если вы плохо определите, или забудете определить реакцию виджета, то будет очень сложно изменять её, когда пользователи уже привыкнут. Если у вас есть сомнения - спросите мнения у окружающих, и, если позволяет бюджет, не стесняйтесь выполнять пользовательские тесты. Этот процесс называется UX Design (Дизайн взаимодействия с пользователем). Если вы хотите узнать больше об этой теме, вам следует посетить следующие полезные ресурсы:

* UXMatters.com
* UXDesign.com
* The UX Design section of SmashingMagazine

**Примечание:** Также, в большинстве систем, есть способ развернуть элемент `<select>` чтобы посмотреть все доступные варианты (это то-же что кликнуть мышью элемент `<select>` ). Это возможно комбинацией Alt+Стрелка вниз для Windows и не реализовано в нашем примере —но это будет просто сделать, так как механизм уже реализован дл события `click`.

### Определение структуры и семантики HTML

Теперь, когда основная функциональность виджета определена, пора начать создание виджета. Первым делом определим его HTML структуру и придадим основную семантику. Вот всё что нам нужно чтобы переделать элемент `<select>`:

```html
<!-- Это основной контейнер для нашего виджета.
 Атрибут tabindex позволяет пользователю переместить фокус на виджет.
 Позже мы увидим, что лучше его установить через JavaScript. -->
<div class="select" tabindex="0">
  <!-- Этот контейнер послужит для отображения текущего значения виджета -->
  <span class="value">Cherry</span>

  <!-- Этот контейнер содержит все варианты. доступные для нашего виджета.
 Так как это список, то есть смысл использовать элемент ul. -->
  <ul class="optList">
    <!-- Каждый вариант содержит то значение, которое будет отображено, позже мы увидим
 как получить то значение, которое будет отправлено вместе с данными формы -->
    <li class="option">Cherry</li>
    <li class="option">Lemon</li>
    <li class="option">Banana</li>
    <li class="option">Strawberry</li>
    <li class="option">Apple</li>
  </ul>
</div>
```

Обратите внимание на использование имён классов: они описывают каждый соответствующий элемент независимо от фактически используемых базовых элементов HTML. Важно быть уверенными что нам не придётся жёстко привязывать наши CSS и JavaScript к HTML структуре,тогда мы сможем позже вносить изменения не нарушая код виджета. Например, если вы захотите создать эквивалент элемента `<optgroup>`.

### Создание внешнего вида с помощью CSS

Теперь у нас есть структура и мы можем заняться дизайном нашего виджета. Весь смысл создания нашего собственного виджета в том, чтобы мы могли придать ему такой внешний вид, как мы захотим. Поэтому мы разделим нашу работу с CSS на две части: в первой части будут правила CSS абсолютно необходимые чтобы реакции нашего виджета были как у элемента `<select>` , а вторая чать будет состоять из красивеньких рюшечек, чтобы виджет выглядел так, как нам нравится.

#### Обязательные стили

Обязательные стили - это те, которые необходимы для обработки трёх состояний нашего виджета..

```css
.select {
  /\* Это создаст контекст позиционирования для списка вариантов \*/
  position: relative;

  /\* Это сделает наш виджет частью текстового потока и одновременно сделает его
 изменяемого размера \*/
  display: inline-block;
}
```

Ещё нам нужен дополнительный класс `active,` чтобы определить, как будет выглядеть наш виджет в активном состоянии. Так как наш виджет может находится в фокусе, то мы укажем этот стиль ещё и для псевдокласса `:focus` чтобы быть уверенными, что виджет будет вести себя одинаково.

```css
.select .active,
.select:focus {
  outline: none;

  /\* Это свойство - box-shadow - необязательно, однако нам важно видеть активное состояние
 т.к. мы используем его по умолчанию. Можете спокойно его переопределить. \*/
  box-shadow: 0 0 3px 1px #227755;
}
```

Теперь давайте стилизуем список опций:

```css
/\* Селектор .select здесь применён для удобства (синтаксический сахар), чтобы быть уверенными,
 что определяемые классы находятся в нашем виджете. \*/
.select .optList {
  /\* Это позволит нам быть уверенными, что список вариантов будет показан ниже значения
 и вне HTML потока \*/
  position: absolute;
  top: 100%;
  left: 0;
}
```

Ещё нам нужен дополнительный класс, для обращения к списку вариантов, когда он скрыт. Это необходимо чтобы справиться с различиями активного и развёрнутого состояния, т.к. они не совсем совпадают.

```css
.select .optList.hidden {
  /\* Это самый простой из доступных способов путь скрыть список,
 а о доступности мы ещё поговорим в конце \*/
  max-height: 0;
  visibility: hidden;
}
```

#### Украшательства

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

```css
.select {
  /\* Все размеры будут выражены в em по соображениям удобства
 (чтобы быть уверенными, что виджет будет изменять размер если пользователь будет
 использовать увеличение в текстовом режиме браузера). Вычисления сделаны из расчёта что
 1em == 16px что является умолчанием для большинства браузеров.
 Если вы затрудняетесь с преобразованием px в em, попробуйте http://riddle.pl/emcalc/ \*/
  font-size: 0.625em; /\* это (10px) новый размер шрифта для нашего контекста для значения
 em в исходном контексте \*/
  font-family: Verdana, Arial, sans-serif;

  -moz-box-sizing: border-box;
  box-sizing: border-box;

  /\* Нам нужно добавить дополнительное пространство для стрелки вниз \*/
  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/
  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  /\* Первое объявление - для браузеров не поддерживающих линейный градиент.
 Второе объявление - потому что основанные на WebKit браузеры ещё не избавились от префикса в нем.
 Если вам нужна поддержка устаревших браузеров, попробуйте http://www.colorzilla.com/gradient-editor/ \*/
  background: #f0f0f0;
  background: -webkit-linear-gradient(90deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  /\* Так как значение может быть шире, чем наш виджет, нужно быть уверенными, что оно не изменит
 ширину виджета \*/
  display: inline-block;
  width: 100%;
  overflow: hidden;

  vertical-align: top;

  /\* И, если содержимое слишком длинное, лучше иметь красивенькие точечки. \*/
  white-space: nowrap;
  text-overflow: ellipsis;
}
```

Нам не нужен дополнительный элемент, чтобы создать стрелку вниз; вместо этого мы используем псевдоэлемент `:after`. Также её можно создать при помощи простого фонового изображения в классе `select`.

```css
.select:after {
  content: "▼"; /\* Мы используем Unicode символ U+25BC; смотрите http://www.utf8-chartable.de \*/
  position: absolute;
  z-index: 1; /\* Важно чтобы стрелка не перекрывала элементы списка \*/
  top: 0;
  right: 0;

  -moz-box-sizing: border-box;
  box-sizing: border-box;

  height: 100%;
  width: 2em; /\* 20px \*/
  padding-top: 0.1em; /\* 1px \*/

  border-left: 0.2em solid #000; /\* 2px \*/
  border-radius: 0 0.1em 0.1em 0; /\* 0 1px 1px 0 \*/

  background-color: #000;
  color: #fff;
  text-align: center;
}
```

Далее стилизуем список вариантов:

```css
.select .optList {
  z-index: 2; /\* Мы явно сказали, что список вариантов всегда будет перекрывать стрелку вниз \*/

  /\* это сбросит значения стиля по умолчанию для элемента ul \*/
  list-style: none;
  margin: 0;
  padding: 0;

  -moz-box-sizing: border-box;
  box-sizing: border-box;

  /\* Это для того, чтобы убедиться что если значения будут короче виджета
 то список вариантов останется таким же по размеру как и сам виджет \*/
  min-width: 100%;

  /\* В случае, если список слишком длинный, его содержимое не будет помещаться по вертикали
 (что автоматически добавит полосу прокрутки), но этого никогда не произойдёт по горизонтали
 (потому что мы не установили ширину и содержимое списка будет регулировать её
 автоматически. Если это будет невозможно - содержимое будет обрезано.) \*/
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;

  border: 0.2em solid #000; /\* 2px \*/
  border-top-width: 0.1em; /\* 1px \*/
  border-radius: 0 0 0.4em 0.4em; /\* 0 0 4px 4px \*/

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4); /\* 0 2px 4px \*/
  background: #f0f0f0;
}
```

Для вариантов нам нужно добавить класс `highlight` чтобы сделать возможным идентифицировать значение которе пользователь выберет (или выбрал).

```css
.select .option {
  padding: 0.2em 0.3em; /\* 2px 3px \*/
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

Итак, вот результат с нашими тремя состояниями (исходный код можно посмотреть здесь (en-US)):

#### Основное состояние
```

<div class="select">
  <span class="value">Cherry</span>
  <ul class="optList hidden">
    <li class="option">Cherry</li>
    <li class="option">Lemon</li>
    <li class="option">Banana</li>
    <li class="option">Strawberry</li>
    <li class="option">Apple</li>
  </ul>
</div>
```

```
.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline-color: transparent;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select::after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

#### Активное состояние
```

<div class="select active">
  <span class="value">Cherry</span>
  <ul class="optList hidden">
    <li class="option">Cherry</li>
    <li class="option">Lemon</li>
    <li class="option">Banana</li>
    <li class="option">Strawberry</li>
    <li class="option">Apple</li>
  </ul>
</div>
```

```
.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline-color: transparent;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select::after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

#### Развёрнутое состояние
```

<div class="select active">
  <span class="value">Cherry</span>
  <ul class="optList">
    <li class="option highlight">Cherry</li>
    <li class="option">Lemon</li>
    <li class="option">Banana</li>
    <li class="option">Strawberry</li>
    <li class="option">Apple</li>
  </ul>
</div>
```

```
.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline-color: transparent;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select::after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #fff;
}
```

Оживляем виджет с помощью JavaScript
------------------------------------

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

**Предупреждение:** Следующий код представлен в образовательных целях и не может быть использован как-есть. Помимо прочего, как мы убедимся, он не пригоден для дальнейшего развития и не будет работать в устаревших браузерах. В нем также есть избыточность которую необходимо оптимизировать использования в рабочем режиме.

**Примечание:** Создание многократно используемых виджетов может быть немного сложнее. W3C Web Component draft является одним из ответов на этот конкретный вопрос. The X-Tag project попытка реализовать эту спецификацию; пожалуйста, посмотрите этот проект.

### Почему не работает?

Прежде чем мы начнём, запомните одну важную вещь о JavaScript: в браузере **это ненадёжная технология**. Когда вы создаёте пользовательские виджеты, вы будете полагаться на JavaScript потому что это необходимое звено для связки. Однако во многих случаях JavaScript невозможно запустить в браузере:

* Пользователь отключил JavaScript: Это самый редкий случай; сейчас очень мало людей отключают JavaScript.
* Скрипт не загружается. Это один из самых распространённых случаев, особенно в мобильном мире, где сеть не очень надёжная.
* Скрипт глючит.Вы должны всегда учитывать эту возможность.
* Скрипт конфликтует со сторонним скриптом. Это может случиться со скриптами отслеживания или любыми букмарклетами (bookmarklets), которые использует пользователь.
* Скрипт конфликтует с расширением браузера или зависит от него (такими как расширение NoScript в Firefox, или расширение NotScripts в Chrome).
* Пользователь использует устаревший браузер, и одна из требуемых функций не поддерживается. Это часто случается, когда вы используете передовые API.

Из-за этого очень важно серьёзно подумать о том, что произойдёт, если JavaScript не сработает. Детальное рассмотрение этой проблемы выходит за рамки данной статьи, поскольку эта проблема тесно связана с тем, насколько универсальным и многократно используемым вы хотите сделать свой сценарий, но мы рассмотрим основы этого в нашем примере.

В нашем примере, если наш JavaScript-код не работает, мы используем стандартный элемент `<select>`. Для этого, нам нужны две вещи.

Во-первых нам нужно добавить стандартный элемент `<select>` перед каждым использованием нашего пользовательского виджета. Это также необходимо, чтобы отправить данные из нашего пользовательского виджета вместе с остальными данными формы; подробнее рассмотрим это позже.

```html
<body class="no-widget">
  <form>
    <select name="myFruit">
      <option>Cherry</option>
      <option>Lemon</option>
      <option>Banana</option>
      <option>Strawberry</option>
      <option>Apple</option>
    </select>

    <div class="select">
      <span class="value">Cherry</span>
      <ul class="optList hidden">
        <li class="option">Cherry</li>
        <li class="option">Lemon</li>
        <li class="option">Banana</li>
        <li class="option">Strawberry</li>
        <li class="option">Apple</li>
      </ul>
    </div>
  </form>
</body>
```

Во-вторых нам нужно два новых класса, чтобы скрыть ненужные элементы (то есть "настоящие" элементы `<select>`, если скрипт запустился, или наш пользовательский виджет, если скрипт не запустился). По умолчанию наш HTML код скрывает наш пользовательский виджет.

```css
.widget select,
.no-widget .select {
  /\* Этот CSS селектор значит:
 - или мы присваиваем классу body значение "widget" и таким образом мы скрываем элемент <select>
 - или мы не меняем класс body, тогда класс body остаётся в значении "no-widget",
 и элементы, чей класс "select" будут скрыты \*/
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}
```

Теперь нам нужен модуль JavaScript, чтобы определить, запущен скрипт или нет. Этот модуль очень простой: если наш скрипт запустится во время загрузки страницы, то он удалит класс класс `no-widget` и добавит класс `widget`, чем поменяет видимость элемента `<select>` и нашего пользовательского виджета.

```js
window.addEventListener("load", function () {
  document.body.classList.remove("no-widget");
  document.body.classList.add("widget");
});
```

#### Без JS

Посмотрите полный исходный код (en-US).
```

<form class="no-widget">
  <select name="myFruit">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select">
    <span class="value">Cherry</span>
    <ul class="optList hidden">
      <li class="option">Cherry</li>
      <li class="option">Lemon</li>
      <li class="option">Banana</li>
      <li class="option">Strawberry</li>
      <li class="option">Apple</li>
    </ul>
  </div>
</form>
```

```
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}
```

#### Вместе с JS

Посмотрите полный исходный код (en-US).
```

<form class="no-widget">
  <select name="myFruit">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select">
    <span class="value">Cherry</span>
    <ul class="optList hidden">
      <li class="option">Cherry</li>
      <li class="option">Lemon</li>
      <li class="option">Banana</li>
      <li class="option">Strawberry</li>
      <li class="option">Apple</li>
    </ul>
  </div>
</form>
```

```
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline-color: transparent;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select::after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

```
window.addEventListener("load", () => {
  const form = document.querySelector("form");

  form.classList.remove("no-widget");
  form.classList.add("widget");
});
```

**Примечание:** Если вы действительно хотите сделать свой код универсальным и многоразовым, то вместо переключения классов гораздо лучше просто добавить класс элементам `<select>` чтобы их скрыть, и динамически добавлять дерево DOM представляющее пользовательский виджет после каждого элемента `<select>` на странице.

### Облегчение работы

В коде который мы собираемся написать, для выполнения всех необходимых действий мы будем использовать стандартный DOM API. Однако, хотя поддержка DOM API в браузерах стала гораздо лучше, все ещё есть нюансы с устраевшеними браузерами (особенно со старым добрым Internet Explorer).

Чтобы избежать неприятностей с устаревшими браузерами есть два способа: использовать отдельный фреймворк такой как jQuery, $dom, prototype, Dojo, YUI, и т.п., или самостоятельно реализовать недостающие функции которые вам нужны (что можно легко сделать через условную загрузку, например используя библиотеку yepnope).

Мы планируем использовать следующие функции (от самых рискованных до самых безопасных):

1. `classList`
2. `addEventListener`
3. `forEach` (This is not DOM but modern JavaScript)
4. `querySelector` and `querySelectorAll`

Помимо доступности этих специфических функций, остаётся ещё одна проблема чтобы начать. Объект возвращаемый функцией `querySelectorAll()` имеет тип `NodeList` что отличается от `Array`. Это важно потому, что объекты `Array` поддерживают функцию `forEach`, а `NodeList` не поддерживает. Так как `NodeList` очень похож на `Array` и нам очень удобно использовать `forEach`, мы можем просто добавить `forEach` к объекту `NodeList` чтобы облегчить нам жизнь, например так:

```js
NodeList.prototype.forEach = function (callback) {
  Array.prototype.forEach.call(this, callback);
};
```

Мы не шутили, когда сказали, что это легко сделать.

### Создание процедур обработки событий

Итак, начало положено, и мы можем приступить к функциям, которые будут использоваться для взаимодействия с пользователем.

```js
// Эта функция будет вызываться каждый раз, когда наш виджет будет деактивирован
// Ей передаётся один параметр
// select : DOM нода класса `select` который должен быть деактивирован
function deactivateSelect(select) {
  // Если виджет не активен, то и делать-то нечего
  if (!select.classList.contains("active")) return;

  // Получаем список опций для нашего виджета
  var optList = select.querySelector(".optList");

  // Закрываем список опций
  optList.classList.add("hidden");

  // и деактивируем сам виджет
  select.classList.remove("active");
}

// Эта функция будет вызываться каждый раз, когда пользователь захочет (де)активировать наш виджет
// Ей передаются два параметра:
// select : DOM нода класса `select` для активации
// selectList : список всех DOM нод с классом `select`
function activeSelect(select, selectList) {
  // Если виджет активен, то и делать-то нечего
  if (select.classList.contains("active")) return;

  // Нам нужно отключить активное состояние всех наших виджетов
  // Так как функция deactivateSelect соответствует всем требованиям
  // функции forEach мы вызываем её без использования промежуточной анонимной функции
  selectList.forEach(deactivateSelect);

  // А теперь мы возвращаем активное состояние нужного виджета
  select.classList.add("active");
}

// Эта функция будет вызываться каждый раз, когда пользователь будет открывать/закрывать список вариантов
// Ей передаётся один параметр:
// select : DOM нода со списком для переключения состояния
function toggleOptList(select) {
  // Список хранится в виджете
  var optList = select.querySelector(".optList");

  // Мы меняем класс виджета чтобы показать/скрыть его
  optList.classList.toggle("hidden");
}

// Эта функция будет вызываться каждый раз, когда нам нужно подсветить вариант
// Ей передаются два параметра:
// select : DOM нода класса `select` содержащая вариант для подсветки
// option : DOM нода класса `option` для подсветки
function highlightOption(select, option) {
  // Мы получаем список всех вариантов доступных в нашем элементе
  var optionList = select.querySelectorAll(".option");

  // Мы удаляем подсветку всех вариантов
  optionList.forEach(function (other) {
    other.classList.remove("highlight");
  });

  // Подсвечиваем нужный вариант
  option.classList.add("highlight");
}
```

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

Далее мы связываем эти функции с соответствующими событиями:

```js
// Мы обрабатываем событие при загрузке документа.
window.addEventListener("load", function () {
  var selectList = document.querySelectorAll(".select");

  // Каждый наш собственный виджет должен быть проинициализирован
  selectList.forEach(function (select) {
    // также как и его элементы `option`
    var optionList = select.querySelectorAll(".option");

    // Когда пользователь проводит мышью над элементом `option`, мы подсвечиваем этот вариант
    optionList.forEach(function (option) {
      option.addEventListener("mouseover", function () {
        // Замечание: использование переменных `select` и `option`
        // ограничено рамками нашей функции.
        highlightOption(select, option);
      });
    });

    // Когда пользователь кликает на наш виджет
    select.addEventListener("click", function (event) {
      // Замечание: использование переменной `select`
      // ограничено рамками нашей функции.

      // Мы переключаем видимость списка вариантов
      toggleOptList(select);
    });

    // Когда виджет получает фокус
    // Виджет получает фокус когда пользователь кликает на него
    // или переходит на него клавишей табуляции
    select.addEventListener("focus", function (event) {
      // Замечание: использование переменных `select` и `selectList`
      // ограничено рамками нашей функции.

      // Мы активируем наш виджет
      activeSelect(select, selectList);
    });

    // Когда виджет теряет фокус
    select.addEventListener("blur", function (event) {
      // Замечание: использование переменной `select`
      // ограничено рамками нашей функции.

      // Мы деактивируем виджет
      deactivateSelect(select);
    });
  });
});
```

В этот момент наш виджет будет изменять состояние в соответствии с нашим дизайном, но не будет обновлять его значение. С этим мы разберёмся дальше.

#### Живой пример

Посмотрите полный исходный код (en-US).
```

<form class="no-widget">
  <select name="myFruit" tabindex="-1">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select" tabindex="0">
    <span class="value">Cherry</span>
    <ul class="optList hidden">
      <li class="option">Cherry</li>
      <li class="option">Lemon</li>
      <li class="option">Banana</li>
      <li class="option">Strawberry</li>
      <li class="option">Apple</li>
    </ul>
  </div>
</form>
```

```
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline-color: transparent;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select::after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

```
function deactivateSelect(select) {
  if (!select.classList.contains("active")) return;

  const optList = select.querySelector(".optList");

  optList.classList.add("hidden");
  select.classList.remove("active");
}

function activeSelect(select, selectList) {
  if (select.classList.contains("active")) return;

  selectList.forEach(deactivateSelect);
  select.classList.add("active");
}

function toggleOptList(select, show) {
  const optList = select.querySelector(".optList");

  optList.classList.toggle("hidden");
}

function highlightOption(select, option) {
  const optionList = select.querySelectorAll(".option");

  optionList.forEach((other) => {
    other.classList.remove("highlight");
  });

  option.classList.add("highlight");
}

window.addEventListener("load", () => {
  const form = document.querySelector("form");

  form.classList.remove("no-widget");
  form.classList.add("widget");
});

window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");

    optionList.forEach((option) => {
      option.addEventListener("mouseover", () => {
        highlightOption(select, option);
      });
    });

    select.addEventListener(
      "click",
      (event) => {
        toggleOptList(select);
      },
      false,
    );

    select.addEventListener("focus", (event) => {
      activeSelect(select, selectList);
    });

    select.addEventListener("blur", (event) => {
      deactivateSelect(select);
    });

    select.addEventListener("keyup", (event) => {
      if (event.key === "Escape") {
        deactivateSelect(select);
      }
    });
  });
});
```

### Обработка значения виджета

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

Самый простой способ сделать это - использовать встроенный виджет который также есть в нашей форме. Такой виджет будет отслеживать значение со всеми встроенными элементами управления, предоставленными браузером, и значение будет отправлено, как обычно, при отправке формы. Нет смысла заново изобретать велосипед, когда все это уже сделано за нас.

Как было показано ранее, у нас есть стандартный виджет `<select>` в качестве запасного варианта для повышения доступности; поэтому мы просто синхронизируем его значение с нашим собственным виджетом:

```js
// Эта функция обновляет отображённое значение и синхронизирует его со стандартным виджетом
// Ей передаётся два параметра:
// select : DOM нода класса `select` содержащая значение которое будет обновлено
// index : индекс выбранного значения
function updateValue(select, index) {
  // Нам нужно получить стандартный виджет для данного пользовательского
  // В нашем примере стандартный виджет является братом (sibling) пользовательского виджета
  var nativeWidget = select.previousElementSibling;

  // Нам также нужно получить значение заполнителя нашего пользовательского виджета
  var value = select.querySelector(".value");

  // И нам нужен весь список вариантов
  var optionList = select.querySelectorAll(".option");

  // Установим значение текущего номера выбранного элемента равным index
  nativeWidget.selectedIndex = index;

  // Соответственно установим значение заполнителя
  value.innerHTML = optionList[index].innerHTML;

  // И мы подсвечиваем соответствующий вариант нашего пользовательского виджета
  highlightOption(select, optionList[index]);
}

// Эта функция возвращает текущий номер выбранного элемента в стандартном виджете
// Ей передаётся один параметр:
// select : DOM нода класса `select` соответствующая стандартному виджету
function getIndex(select) {
  // Нам нужно получить доступ к стандартному виджету соответствующему данному
  // пользовательскому виджету
  // В нашем примере стандартный виджет - брат (sibling) пользовательского виджета
  var nativeWidget = select.previousElementSibling;

  return nativeWidget.selectedIndex;
}
```

Используя эти две функции мы можем связать стандартный виджет с пользовательским:

```js
// Мы обрабатываем привязку события при загрузке документа.
window.addEventListener("load", function () {
  var selectList = document.querySelectorAll(".select");

  // Каждый пользовательский виджет необходимо инициализировать:
  selectList.forEach(function (select) {
    var optionList = select.querySelectorAll(".option"),
      selectedIndex = getIndex(select);

    // Мы делаем наш пользовательский виджет фокусируемым
    select.tabIndex = 0;

    // Мы делаем стандартный виджет более не фокусируемым
    select.previousElementSibling.tabIndex = -1;

    // Убеждаемся, что выбранное по умолчанию значение корректно отображено
    updateValue(select, selectedIndex);

    // Каждый раз, когда пользователь кликает на вариант, мы соответственно обновляем значение
    optionList.forEach(function (option, index) {
      option.addEventListener("click", function (event) {
        updateValue(select, index);
      });
    });

    // Когда виджет находится в фокусе, при каждом нажатии клавиши на клавиатуре, мы соответственно
    // обновляем значение
    select.addEventListener("keyup", function (event) {
      var length = optionList.length,
        index = getIndex(select);

      // Когда пользователь нажимает стрелку вниз, мы переходим на следующий вариант
      if (event.keyCode === 40 && index < length - 1) {
        index++;
      }

      // Когда пользователь нажимает стрелку вверх, мы переходим на предыдущий вариант
      if (event.keyCode === 38 && index > 0) {
        index--;
      }

      updateValue(select, index);
    });
  });
});
```

В приведённом выше коде стоить отметить свойство `tabIndex` (en-US). Использование этого свойства необходимо чтобы стандартный виджет никогда не получил фокус, и чтобы убедиться, что наш пользовательский виджет получает фокус когда пользователь использует клавиатуру или мышь.

С этим мы закончили! Вот результат:

#### Живой пример

Посмотрите полный исходный код (en-US).
```

<form class="no-widget">
  <select name="myFruit">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select">
    <span class="value">Cherry</span>
    <ul class="optList hidden">
      <li class="option">Cherry</li>
      <li class="option">Lemon</li>
      <li class="option">Banana</li>
      <li class="option">Strawberry</li>
      <li class="option">Apple</li>
    </ul>
  </div>
</form>
```

```
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline-color: transparent;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select::after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

```
function deactivateSelect(select) {
  if (!select.classList.contains("active")) return;

  const optList = select.querySelector(".optList");

  optList.classList.add("hidden");
  select.classList.remove("active");
}

function activeSelect(select, selectList) {
  if (select.classList.contains("active")) return;

  selectList.forEach(deactivateSelect);
  select.classList.add("active");
}

function toggleOptList(select, show) {
  const optList = select.querySelector(".optList");

  optList.classList.toggle("hidden");
}

function highlightOption(select, option) {
  const optionList = select.querySelectorAll(".option");

  optionList.forEach((other) => {
    other.classList.remove("highlight");
  });

  option.classList.add("highlight");
}

function updateValue(select, index) {
  const nativeWidget = select.previousElementSibling;
  const value = select.querySelector(".value");
  const optionList = select.querySelectorAll(".option");

  nativeWidget.selectedIndex = index;
  value.innerHTML = optionList[index].innerHTML;
  highlightOption(select, optionList[index]);
}

function getIndex(select) {
  const nativeWidget = select.previousElementSibling;

  return nativeWidget.selectedIndex;
}

window.addEventListener("load", () => {
  const form = document.querySelector("form");

  form.classList.remove("no-widget");
  form.classList.add("widget");
});

window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");

    optionList.forEach((option) => {
      option.addEventListener("mouseover", () => {
        highlightOption(select, option);
      });
    });

    select.addEventListener("click", (event) => {
      toggleOptList(select);
    });

    select.addEventListener("focus", (event) => {
      activeSelect(select, selectList);
    });

    select.addEventListener("blur", (event) => {
      deactivateSelect(select);
    });
  });
});

window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");
    const selectedIndex = getIndex(select);

    select.tabIndex = 0;
    select.previousElementSibling.tabIndex = -1;

    updateValue(select, selectedIndex);

    optionList.forEach((option, index) => {
      option.addEventListener("click", (event) => {
        updateValue(select, index);
      });
    });

    select.addEventListener("keyup", (event) => {
      let index = getIndex(select);

      if (event.key === "Escape") {
        deactivateSelect(select);
      }
      if (event.key === "ArrowDown" && index < optionList.length - 1) {
        index++;
      }
      if (event.key === "ArrowUp" && index > 0) {
        index--;
      }

      updateValue(select, index);
    });
  });
});
```

Но подождите, мы точно закончили?

Добавляем доступность
---------------------

Мы создали нечто работающее, и, хотя это далеко от полнофункционального выпадающего списка, работает это хорошо. Однако то, что мы сделали, это не более, чем возня с DOM. У него нет настоящей семантики, и хотя оно выглядит как раскрывающейся список, с точки зрения браузера - это не так, поэтому вспомогательные технологии не смогут понять что это он и есть. Короче говоря, этот хорошенький список для выбора фруктов не является доступным для людей с ограниченными возможностями!

К счастью существует решение, и оно называется ARIA (en-US). ARIA - аббревиатура для "Accessible Rich Internet Application" (Доступное всем интернет приложение), и представляет собой W3C спецификацию специально разработанную для того, что мы здесь делаем: делаем веб приложения и пользовательские виджеты ассистивными (доступными для людей с ограниченными возможностями). В основном, это набор атрибутов, которые расширяют HTML, чтобы мы смогли лучше описать роли, состояния и свойства, так что только что изобретённый элемент выглядит как будто он был тем стандартным, за которого он себя выдаёт. Использовать эти атрибуты очень просто, поэтому давайте сделаем это.

### Атрибут `role`

Ключевой атрибут используемый в ARIA (en-US) - это `role` (en-US). Атрибут `role` (en-US) принимает значение, определяющее для чего используется элемент. Каждая роль определяет свои собственные требования и поведение. В нашем примере мы используем роль `listbox` (en-US). Это "составная роль" ("composite role"), т.е. элементы такой роли имеют потомков, у каждого из которых есть отдельная роль (в данном случае, как минимум один дочерний элемент с ролью `option`).

Стоит также отметить что ARIA определяет роли, которые по умолчанию применяются к стандартной разметке HTML. Например, элемент `<table>` (en-US) соответствует роли `grid`, а элемент `<ul>` соответствует роли `list`. Так как мы используем элемент `<ul>`, то нам необходимо убедиться что роль `listbox` нашего виджета заменит роль `list` элемента `<ul>`. С этой целью, мы будем использовать роль `presentation`. Эта роль разработана чтобы можно было отметить, что элемент не имеет особого значения, а используется исключительно чтобы представить информацию. Мы применим его к нашему элементу `<ul>`.

Чтобы ввести роль `listbox` (en-US) нам нужно просто внести следующие изменения в HTML:

```html
<!-- Мы добавили атрибут role="listbox" в наш элемент верхнего уровня -->
<div class="select" role="listbox">
  <span class="value">Cherry</span>
  <!-- Также мы добавили атрибут role="presentation" в элемент ul -->
  <ul class="optList" role="presentation">
    <!-- И мы добавили атрибут role="option" во все элементы li -->
    <li role="option" class="option">Cherry</li>
    <li role="option" class="option">Lemon</li>
    <li role="option" class="option">Banana</li>
    <li role="option" class="option">Strawberry</li>
    <li role="option" class="option">Apple</li>
  </ul>
</div>
```

**Примечание:** Включение как атрибута `role` так и атрибута `class` необходимо только если вы хотите обеспечить поддержку устаревших браузеров, которые не поддерживают селекторы атрибутов CSS (en-US).

### Атрибут `aria-selected`

Использовать только атрибут `role` (en-US) недостаточно. ARIA (en-US) также предоставляет множество атрибутов состояний и свойств. Чем больше и уместнее вы их используете, тем ваш виджет будет более понятен для вспомогательных технологий. В нашем случае мы ограничимся использованием одного атрибута: `aria-selected`.

Атрибут `aria-selected` используется для отметки текущего выбранного варианта; это позволяет ассистивным технологиям информировать пользователя о текущем выборе. Мы будем используя JavaScript динамически отмечать выбранный вариант каждый раз, когда пользователь его выбирает. С этой целью нам нужно пересмотреть нашу функцию `updateValue()`:

```js
function updateValue(select, index) {
  var nativeWidget = select.previousElementSibling;
  var value = select.querySelector(".value");
  var optionList = select.querySelectorAll(".option");

  // Мы уверены что все варианты не выбраны
  optionList.forEach(function (other) {
    other.setAttribute("aria-selected", "false");
  });

  // Мы уверены что выбранный вариант отмечен
  optionList[index].setAttribute("aria-selected", "true");

  nativeWidget.selectedIndex = index;
  value.innerHTML = optionList[index].innerHTML;
  highlightOption(select, optionList[index]);
}
```

Ниже окончательный результат всех этих изменений (для большей наглядности попробуйте эти примеры со вспомогательными технологиями, такими как NVDA или VoiceOver).

#### Живой пример

Посмотрите полный исходный код (en-US).
```

<form class="no-widget">
  <select name="myFruit">
    <option>Cherry</option>
    <option>Lemon</option>
    <option>Banana</option>
    <option>Strawberry</option>
    <option>Apple</option>
  </select>

  <div class="select" role="listbox">
    <span class="value">Cherry</span>
    <ul class="optList hidden" role="presentation">
      <li class="option" role="option" aria-selected="true">Cherry</li>
      <li class="option" role="option">Lemon</li>
      <li class="option" role="option">Banana</li>
      <li class="option" role="option">Strawberry</li>
      <li class="option" role="option">Apple</li>
    </ul>
  </div>
</form>
```

```
.widget select,
.no-widget .select {
  position: absolute;
  left: -5000em;
  height: 0;
  overflow: hidden;
}

.select {
  position: relative;
  display: inline-block;
}

.select.active,
.select:focus {
  box-shadow: 0 0 3px 1px #227755;
  outline-color: transparent;
}

.select .optList {
  position: absolute;
  top: 100%;
  left: 0;
}

.select .optList.hidden {
  max-height: 0;
  visibility: hidden;
}

.select {
  font-size: 0.625em; /\* 10px \*/
  font-family: Verdana, Arial, sans-serif;

  box-sizing: border-box;

  padding: 0.1em 2.5em 0.2em 0.5em; /\* 1px 25px 2px 5px \*/
  width: 10em; /\* 100px \*/

  border: 0.2em solid #000; /\* 2px \*/
  border-radius: 0.4em; /\* 4px \*/

  box-shadow: 0 0.1em 0.2em rgba(0, 0, 0, 0.45); /\* 0 1px 2px \*/

  background: #f0f0f0;
  background: linear-gradient(0deg, #e3e3e3, #fcfcfc 50%, #f0f0f0);
}

.select .value {
  display: inline-block;
  width: 100%;
  overflow: hidden;

  white-space: nowrap;
  text-overflow: ellipsis;
  vertical-align: top;
}

.select::after {
  content: "▼";
  position: absolute;
  z-index: 1;
  height: 100%;
  width: 2em; /\* 20px \*/
  top: 0;
  right: 0;

  padding-top: 0.1em;

  box-sizing: border-box;

  text-align: center;

  border-left: 0.2em solid #000;
  border-radius: 0 0.1em 0.1em 0;

  background-color: #000;
  color: #fff;
}

.select .optList {
  z-index: 2;

  list-style: none;
  margin: 0;
  padding: 0;

  background: #f0f0f0;
  border: 0.2em solid #000;
  border-top-width: 0.1em;
  border-radius: 0 0 0.4em 0.4em;

  box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.4);

  box-sizing: border-box;

  min-width: 100%;
  max-height: 10em; /\* 100px \*/
  overflow-y: auto;
  overflow-x: hidden;
}

.select .option {
  padding: 0.2em 0.3em;
}

.select .highlight {
  background: #000;
  color: #ffffff;
}
```

```
function deactivateSelect(select) {
  if (!select.classList.contains("active")) return;

  const optList = select.querySelector(".optList");

  optList.classList.add("hidden");
  select.classList.remove("active");
}

function activeSelect(select, selectList) {
  if (select.classList.contains("active")) return;

  selectList.forEach(deactivateSelect);
  select.classList.add("active");
}

function toggleOptList(select, show) {
  const optList = select.querySelector(".optList");

  optList.classList.toggle("hidden");
}

function highlightOption(select, option) {
  const optionList = select.querySelectorAll(".option");

  optionList.forEach((other) => {
    other.classList.remove("highlight");
  });

  option.classList.add("highlight");
}

function updateValue(select, index) {
  const nativeWidget = select.previousElementSibling;
  const value = select.querySelector(".value");
  const optionList = select.querySelectorAll(".option");

  optionList.forEach((other) => {
    other.setAttribute("aria-selected", "false");
  });

  optionList[index].setAttribute("aria-selected", "true");

  nativeWidget.selectedIndex = index;
  value.innerHTML = optionList[index].innerHTML;
  highlightOption(select, optionList[index]);
}

function getIndex(select) {
  const nativeWidget = select.previousElementSibling;

  return nativeWidget.selectedIndex;
}

window.addEventListener("load", () => {
  const form = document.querySelector("form");

  form.classList.remove("no-widget");
  form.classList.add("widget");
});

window.addEventListener("load", () => {
  const selectList = document.querySelectorAll(".select");

  selectList.forEach((select) => {
    const optionList = select.querySelectorAll(".option");
    const selectedIndex = getIndex(select);

    select.tabIndex = 0;
    select.previousElementSibling.tabIndex = -1;

    updateValue(select, selectedIndex);

    optionList.forEach((option, index) => {
      option.addEventListener("mouseover", () => {
        highlightOption(select, option);
      });

      option.addEventListener("click", (event) => {
        updateValue(select, index);
      });
    });

    select.addEventListener("click", (event) => {
      toggleOptList(select);
    });

    select.addEventListener("focus", (event) => {
      activeSelect(select, selectList);
    });

    select.addEventListener("blur", (event) => {
      deactivateSelect(select);
    });

    select.addEventListener("keyup", (event) => {
      let index = getIndex(select);

      if (event.key === "Escape") {
        deactivateSelect(select);
      }
      if (event.key === "ArrowDown" && index < optionList.length - 1) {
        index++;
      }
      if (event.key === "ArrowUp" && index > 0) {
        index--;
      }

      updateValue(select, index);
    });
  });
});
```

Заключение
----------

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

Вот несколько библиотек, которые вам стоит рассмотреть перед тем как создавать собственную:

* jQuery UI
* msDropDown
* Nice Forms
* и многие другие…

Если вы хотите двигаться далее, то код в этом примере нуждается в некотором улучшении прежде чем станет универсальным и многоразовым. Это упражнение, которое вы можете попробовать выполнить. Две подсказки, которые помогут вам в этом: первый аргумент всех наших функций одинаков, это значит что эти функции должны быть в одном контексте. Было бы разумным создать объект для совместного использования этого контекста. Также вам нужно сделать его функциональным; это значит, что ему необходимо одинаково хорошо работать с различными браузерами, чья совместимость с Web стандартами очень отличается. Повеселись!

* Назад  (en-US)
* Обзор: Forms
* Далее


Основы CSS - Изучение веб-разработки

Основы CSS
==========

* Назад
* Обзор: Getting started with the web
* Далее

CSS (Cascading Style Sheets) — это код, который вы используете для стилизации вашей веб-страницы. *Основы CSS* помогут вам понять, что вам нужно для начала работы. Мы ответим на такие вопросы как: Как сделать мой текст черным или красным? Как сделать так, чтобы контент появлялся в определённом месте на экране? Как украсить мою веб-страницу с помощью фоновых изображений и цветов?

Так что же такое CSS?
---------------------

Как и HTML, CSS на самом деле не является языком программирования. Это не язык разметки - это *язык таблицы стилей.* Это означает, что он позволяет применять стили выборочно к элементам в документах HTML. Например, чтобы выбрать **все** элементы абзаца на HTML странице и изменить текст внутри них с чёрного на красный, вы должны написать этот CSS:
```

p {
  color: red;
}
```

Давайте попробуем: вставьте эти три строки CSS в новый файл в ваш текстовый редактор, а затем сохраните файл как `style.css` в вашей папке `styles`.

Но нам всё равно нужно применить CSS к нашему HTML документу. В противном случае, CSS стиль не повлияет на то, как ваш браузер отобразит HTML документ. (Если вы не следили за нашим проектом, то прочитайте раздел Работа с файлами и Основы HTML, чтобы узнать, что вам нужно сделать в первую очередь.)

1. Откройте ваш файл `index.html` и вставьте следующую строку куда-нибудь в шапку, между `<head>` и `</head>` тегами:

```html
<link href="styles/style.css" rel="stylesheet" type="text/css" />
```

2. Сохраните `index.html` и загрузите его в вашем браузере. Вы должны увидеть что-то вроде этого:

![A mozilla logo and some paragraphs. The paragraph text has been styled red by our css.](/ru/docs/Learn/Getting_started_with_the_web/CSS_basics/website-screenshot-styled.png)Если текст вашего абзаца теперь красный, примите наши поздравления! Вы написали свой первый успешный CSS!

### Анатомия набора правил CSS

Давайте взглянем на вышеупомянутый CSS немного более подробно:

![](/ru/docs/Learn/Getting_started_with_the_web/CSS_basics/css-declaration-small.png)

Вся структура называется **набором правил** (но зачастую для краткости "правило"). Отметим также имена отдельных частей:

Селектор (Selector)

Имя HTML-элемента в начале набора правил. Он выбирает элемент(ы) для применения стиля (в данном случае, элементы `p` ). Для стилизации другого элемента, просто измените селектор.

Объявление (Declaration)

Единственное правило, например `color: red;` указывает, какие из **свойств** элемента вы хотите стилизовать.

Свойства (Properties)

Способы, которыми вы можете стилизовать определённый HTML-элемент (в данном случае, `color` является свойством для элементов `<p>`). В CSS вы выбираете, какие свойства вы хотите затронуть в вашем правиле.

Значение свойства (Property value)

Справа от свойства, после двоеточия, у нас есть **значение свойства**, которое выбирает одно из множества возможных признаков для данного свойства (существует множество значений `color`, помимо `red`).

Обратите внимание на важные части синтаксиса:

* Каждый набор правил (кроме селектора) должен быть обёрнут в фигурные скобки (`{}`).
* В каждом объявлении необходимо использовать двоеточие (`:`), чтобы отделить свойство от его значений.
* В каждом наборе правил вы должны использовать точку с запятой (`;`), чтобы отделить каждое объявление от следующего.

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

```css
p {
  color: red;
  width: 500px;
  border: 1px solid black;
}
```

### Выбор нескольких элементов

Вы также можете выбрать несколько элементов разного типа и применить единый набор правил для всех из них. Добавьте несколько селекторов, разделённых запятыми. Например:

```css
p,
li,
h1 {
  color: red;
}
```

### Разные типы селекторов

Существует множество различных типов селектора. Выше мы рассматривали только **селектор элементов**, который выбирает все элементы определённого типа в HTML документе. Но мы можем сделать выбор более конкретным. Вот некоторые из наиболее распространённых типов селекторов:

| Имя селектора | Что выбирает | Пример |
| --- | --- | --- |
| Селектор элемента (иногда называемый селектором тега или типа) | Все HTML-элемент(ы) указанного типа. | `p` Выбирает `<p>` |
| ID селектор | Элемент на странице с указанным ID на данной HTML. Лучше всего использовать один элемент для каждого ID (и конечно один ID для каждого элемента), даже если вам разрешено использовать один и тот же ID для нескольких элементов. | `#my-id` Выбирает `<p id="my-id">` или `<a id="my-id">` |
| Селектор класса | Элемент(ы) на странице с указанным классом (множество экземпляров класса может объявляться на странице). | `.my-class` Выбирает `<p class="my-class">` и `<a class="my-class">` |
| Селектор атрибута | Элемент(ы) на странице с указанным атрибутом. | `img[src]` Выбирает `<img src="myimage.png">` но не `<img>` |
| Селектор псевдокласса | Указанные элемент(ы), но только в случае определённого состояния, например, при наведении курсора. | `a:hover` Выбирает `<a>`, но только тогда, когда указатель мыши наведён на ссылку. |

Существует ещё много селекторов для изучения, и вы можете найти более подробный список в нашем Руководстве селекторов (en-US).

Шрифты и текст
--------------

Теперь, когда мы изучили некоторые основы CSS, давайте добавим ещё несколько правил и информацию в наш файл `style.css`, чтобы наш пример хорошо выглядел. Прежде всего, давайте сделаем, чтобы наши шрифты и текст выглядели немного лучше.

1. Прежде всего, вернитесь и найдите вывод из Google Fonts, который вы уже где-то сохранили. Добавьте элемент `<link>` где-нибудь внутри шапки вашего `index.html` (снова, в любом месте между тегами `<head>` и `</head>`). Это будет выглядеть примерно так:

```html
<link
 href="http://fonts.googleapis.com/css?family=Open+Sans"
 rel="stylesheet"
 type="text/css" />
```

Этот код связывает вашу страницу с таблицой стилями, которая загружает семейство шрифтов Open Sans вместе с вашей страницей и позволяет вам применять их к вашим HTML-элементам используя свою собственную таблицу стилей.
2. Затем, удалите существующее правило в вашем `style.css` файле. Это был хороший тест, но красный текст, на самом деле, не очень хорошо выглядит.
3. Добавьте следующие строки в нужное место, заменив строку `placeholder` актуальной `font-family` строкой, которую вы получили из Google Fonts. (`font-family` просто означает, какой шрифт(ы) вы хотите использовать для вашего текста). Это правило устанавливает глобальный базовый шрифт и размер шрифта для всей страницы (поскольку `<html>` является родительским элементом для всей страницы, и все элементы внутри него наследуют такой же `font-size` и `font-family`):

```css
html {
  font-size: 10px; /\* px значит 'пиксели': базовый шрифт будет 10 пикселей в высоту \*/
  font-family: placeholder: здесь должно быть имя шрифта из Google fonts
}
```

**Примечание:** Все в CSS документе между `/*` и `*/` является **CSS комментарием**, который браузер игнорирует при исполнении кода. Это место, где вы можете написать полезные заметки о том, что вы делаете.
4. Теперь мы установим размер шрифта для элементов, содержащих текст внутри HTML тела (`<h1>` (en-US), `<li>`, и `<p>`). Мы также отцентрируем текст нашего заголовка и установим некоторую высоту строки и расстояние между буквами в теле документа, чтобы сделать его немного более удобным для чтения:

```css
h1 {
  font-size: 60px;
  text-align: center;
}

p,
li {
  font-size: 16px;
  line-height: 2;
  letter-spacing: 1px;
}
```

Вы можете настроить значения `px` так, как вам нравится, чтобы ваш дизайн выглядел так, как вы хотите, но, в общем, ваш дизайн должен выглядеть вот так:

![a mozilla logo and some paragraphs. a sans-serif font has been set, the font sizes, line height and letter spacing are adjusted, and the main page heading has been centered](/ru/docs/Learn/Getting_started_with_the_web/CSS_basics/website-screenshot-font-small.png)

Блоки, блоки и ещё раз блоки
----------------------------

Одна вещь, которую вы заметите в написании CSS, заключается в том, что многое из этого касается блоков - настройка их размера, цвета, положения и т.д. Большинство HTML-элементов на странице можно рассматривать как блоки, расположенные друг над другом.

![a big stack of boxes or crates sat on top of one another](/ru/docs/Learn/Getting_started_with_the_web/CSS_basics/boxes.jpg)

Не удивительно, макет CSS основан, главным образом, на *блочной модели (box model)*. Каждый из блоков, занимающий пространство на вашей странице имеет такие свойства, как:

* `padding`, пространство только вокруг контента (например, вокруг абзаца текста)
* `border`, сплошная линия, которая расположена рядом с padding
* `margin`, пространство вокруг внешней стороны элемента

![three boxes sat inside one another. From outside to in they are labelled margin, border and padding](/ru/docs/Learn/Getting_started_with_the_web/CSS_basics/box-model.png)

В этом разделе мы также используем:

* `width` (ширину элемента)
* `background-color`, цвет позади контента и padding элементов
* `color`, цвет контента элемента (обычно текста)
* `text-shadow`: устанавливает тень на тексте внутри элемента
* `display`: устанавливает режим отображения элемента (пока что не волнуйтесь об этом)

Итак, давайте начнём и добавим больше CSS на нашей странице! Продолжайте добавлять эти новые правила, расположенные в нижней части страницы, и не бойтесь экспериментировать с изменением значений, чтобы увидеть, как это работает.

### Изменение цвета страницы

```css
html {
  background-color: #00539f;
}
```

Это правило устанавливает цвет фона для всей страницы. Измените код цвета сверху, на цвет который вы выбрали при планировании вашего сайта.

### Разбираемся с телом

```css
body {
  width: 600px;
  margin: 0 auto;
  background-color: #ff9500;
  padding: 0 20px 20px 20px;
  border: 5px solid black;
}
```

Теперь для `<body>` элемента. Здесь есть немало деклараций, так что давайте пройдём через них всех по одному:

* `width: 600px;` — заставляет тело быть всегда 600 пикселей в ширину.
* `margin: 0 auto;` — когда вы устанавливаете два значения для таких свойств как `margin` или `padding`, первое значение элемента влияет на верхнюю **и** нижнюю сторону (делает их `0` в данном случае), и второе значение на левую **и** правую сторону (здесь, `auto` является особым значением, которое делит доступное пространство по горизонтали поровну слева и справа). Вы также можете использовать один, три или четыре значения, как описано здесь.
* `background-color: #FF9500;` — как и прежде, устанавливает цвет фона элемента. Я использовал красновато-оранжевый для тела, в отличие от темно-синего цвета для `<html>` элемента, но не стесняйтесь и экспериментируйте.
* `padding: 0 20px 20px 20px;` — у нас есть четыре значения, установленные для padding, чтобы сделать немного пространства вокруг нашего контента. В этот раз мы не устанавливаем padding на верхней части тела, но делаем 20 пикселей слева, снизу и справа. Значения устанавливаются сверху, справа, снизу, слева, в таком порядке.
* `border: 5px solid black;` — просто устанавливает сплошную чёрную рамку шириной 5 пикселей со всех сторон тела.
### Позиционирование и стилизация нашего заголовка главной страницы

```css
h1 {
  margin: 0;
  padding: 20px 0;
  color: #00539f;
  text-shadow: 3px 3px 1px black;
}
```

Вы, возможно, заметили, что есть ужасный разрыв в верхней части тела. Это происходит, потому что браузеры применяют некоторый **стиль по умолчанию** для элемента `<h1>` (en-US) (по сравнению с другими), даже если вы не применяли какой-либо CSS вообще! Это может звучать как плохая идея, но мы хотим, чтобы веб-страница без стилей имела базовую читаемость. Чтобы избавиться от разрыва, мы переопределили стиль по умолчанию, установив `margin: 0;`.

Затем мы установили заголовку верхний и нижний padding на 20 пикселей, и сделали текст заголовка того же цвета, как и цвет фона html.

Здесь, мы использовали одно довольно интересное свойство - это `text-shadow`, которое применяет тень к текстовому контенту элемента. Оно имеет следующие четыре значения:

* Первое значение пикселей задаёт **горизонтальное смещение** тени от текста — как далеко она движется поперёк: отрицательное значение должно двигать её влево.
* Второе значение пикселей задаёт **вертикальное смещение** тени от текста — как далеко она движется вниз, в этом примере: отрицательное значение должно переместить её вверх.
* Третье значение пикселей задаёт **радиус размытия** тени — большее значение будет означать более размытую тень.
* Четвёртое значение задаёт основной цвет тени.

И вновь попробуйте поэкспериментировать с различными значениями, чтобы посмотреть, что вы можете придумать.

### Центрирование изображения

```css
img {
  display: block;
  margin: 0 auto;
}
```

В заключение, мы отцентрируем изображение, чтобы оно лучше выглядело. Мы можем использовать `margin: 0 auto` уловку снова, как мы это делали раньше для body, но мы также должны сделать кое-что ещё. Элемент `<body>` является **блочным**, это значит, что он занимает всю ширину страницы и может иметь margin и другие значения отступов, применяемых к нему. Изображения, наоборот, являются **строчными** элементами, то есть они этого не могут. Таким образом, чтобы применить margin к изображению, мы должны дать изображению блочное поведение с помощью `display: block;`.

**Примечание:** Приведённые выше инструкции предполагают, что вы используете изображение меньшей ширины, чем заданная для `<body>` (600px). Если ваше изображение больше, тогда оно выйдет за границы `<body>` и займёт пространство страницы. Чтобы исправить это, вы можете: 1) уменьши ширину изображения используя графический редактор, или 2) изменить размер изображения используя CSS путём установки свойства `width` для `<img>` меньше, чем у `<body>`.

**Примечание:** Не стоит беспокоиться, если вы ещё не понимаете `display: block;` и различия между блочным/строчным. Вы поймёте, когда будете изучать CSS более подробно. Вы можете узнать больше о различных доступных значениях display на нашей странице о свойстве display.

Заключение
----------

Если вы следовали всем инструкциям в этой статье, вы должны получить страницу, которая выглядит примерно так (вы также можете посмотреть нашу версию здесь):

![a mozilla logo, centered, and a header and paragraphs. It now looks nicely styled, with a blue background for the whole page and orange background for the centered main content strip.](/ru/docs/Learn/Getting_started_with_the_web/CSS_basics/website-screenshot-final.png)

Если вы застряли, вы всегда можете сравнить свою работу с нашим готовым примером кода на Github.

Здесь мы узнали только самую поверхность CSS. Чтобы узнать больше, перейдите на нашу страницу изучения CSS.

* Назад
* Обзор: Getting started with the web
* Далее


Установка базового программного обеспечения - Изучение веб-разработки

Установка базового программного обеспечения
===========================================

* Обзор: Getting started with the web
* Далее

В *Установке базового программного обеспечения*, мы покажем вам, какие инструменты вам необходимы для простой веб-разработки, и как правильно установить их.

Какие инструменты используют профессионалы?
-------------------------------------------

* **Компьютер**. Может быть, это звучит очевидно для некоторых людей, но некоторые из вас читают эту статью с телефона или библиотечного компьютера. Для серьёзной веб-разработки, лучше приобрести настольный компьютер (Windows, Mac или Linux).
* **Текстовый редактор**, чтобы писать код. Это может быть текстовый редактор (например, Visual Studio Code, Notepad++, Sublime Text, Atom, GNU Emacs, или VIM) или гибридный редактор (например, Dreamweaver or WebStorm). Офисные редакторы не подходят для этого, поскольку они зависят от скрытых элементов, которые мешают движкам рендеринга, используемым веб-браузерами.
* **Веб-браузеры**, для тестирования кода. В настоящее время наиболее часто используемые браузеры это Firefox, Chrome, Opera, Safari, и Internet Explorer. Вы также должны тестировать ваш сайт на то, как он работает на мобильных устройствах и на любых старых браузерах, которые ваша целевая аудитория может все ещё широко использовать (например, IE 6-8). Lynx, текстовый веб-браузер для терминала, отлично подходит для того, чтобы увидеть, как ваш сайт воспринимается слабовидящими пользователями.
* **Графический редактор**, такие как The Gimp, Paint.NET, или Photoshop, чтобы создавать изображения для ваших веб-страниц.
* **Система контроля версий**, чтобы управлять файлами на сервере, для совместной работы над проектом с командой, обмениваться кодом и избегать редакторских конфликтов. Сейчас Git является наиболее популярным инструментом контроля версий, и репозиторий кода Github, основанный на Git, также является очень популярным.
* **FTP программа**, чтобы загружать веб-страницы на сервер для публичного просмотра. Существует обилие таких программ, доступных в том числе Cyberduck, Fetch, и FileZilla.
* **Система автоматизации**, такая как Grunt или Gulp, для автоматического выполнения повторяющихся задач, например, минимизации кода и запуска тестов.
* Шаблоны, библиотеки, фреймворки и т. д., чтобы ускорить написание общей функциональности.
* А также некоторые другие инструменты!
Какие инструменты на самом деле нужны мне прямо сейчас?
-------------------------------------------------------

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

### Установка текстового редактора

У вас, наверное, уже есть базовый текстовый редактор на вашем компьютере. По умолчанию Windows включает Блокнот и Mac OS X поставляется с TextEdit. Linux дистрибутивы варьируются; Ubuntu поставляется с gedit по умолчанию.

Для веб-разработки вам, вероятно, понадобится больше, чем могут предложить Notepad или TextEdit. Мы рекомендуем начать с Visual Studio Code, который является бесплатным редактором, который предлагает предварительный просмотр и подсказки во время написания кода.

### Установка современных веб-браузеров

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

* Linux: Firefox, Chrome, Opera, Brave.
* Windows: Firefox, Chrome, Opera, Internet Explorer, Brave (Если у вас есть Windows 8 или выше, вы можете установить IE 10 или более позднюю версию; иначе, вам следует установить альтернативный браузер)
* Mac: Firefox, Chrome, Opera, Safari, Brave (Safari поставляется с iOS и OS X по умолчанию)

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

### Установка локального веб-сервера

Некоторые примеры для успешной работы необходимо будет запустить на веб-сервере. Вы можете узнать, как это сделать в статье Как настроить локальный сервер для тестирования?

* Обзор: Getting started with the web
* Далее


Каким должен быть ваш веб-сайт? - Изучение веб-разработки

Каким должен быть ваш веб-сайт?
===============================

* Назад
* Обзор: Getting started with the web
* Далее

*Каким должен быть ваш веб-сайт?* обсудите план и дизайн веб-сайта, *прежде* чем приступить к написанию кода, в том числе "Какую информацию будет содержать мой веб-сайт?" "Какие шрифты и цвета я хочу использовать?" "Что будет делать мой сайт?"

Перво-наперво: планирование
---------------------------

Перед тем как делать что-то, вам нужны идеи. Что ваш веб-сайт должен фактически делать? По существу, ваш веб-сайт может делать все, что угодно, но для вашей первой попытки, вы должны придерживаться простых вещей. Мы начнём с создания простой веб-страницы, содержащую заголовок, изображение и несколько абзацев.

Для начала, вам будет нужно ответить на следующие вопросы:

1. **О чем ваш веб-сайт?** вам нравятся собаки, Нью-Йорк или Pacman?
2. **Какую информацию вы предоставляете о предмете?** Напишите заголовок и несколько абзацев, и подумайте над изображениями, которые вы хотите показать на своей странице.
3. **Как будет выглядеть ваш веб-сайт**, в простых терминах высокого уровня. Какой цвет фона? Какой вид шрифта будет уместен: деловой, мультяшный, жирный и кричащий или тонкий?

**Примечание:** Комплексные проекты нуждаются в детализированных руководствах, которые включают все детали цветов, шрифтов, расстояния между элементами на странице, соответствующий стиль письма и так далее. Их иногда называют руководствами по проектированию или бренд-бук, вы можете увидеть пример в Руководство Firefox OS.

Сделайте набросок вашего дизайна
--------------------------------

Теперь возьмите ручку и бумагу и сделайте примерный набросок того, как вы хотите, чтобы выглядел ваш сайт. Для вашей первой веб-страницы должен получиться небольшой набросок, и вы должны взять это в привычку. Это действительно помогает, и вам не нужно быть Ван Гогом!

![](/ru/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like/website-drawing-scan.png)

**Примечание:** Даже в реальных, сложных веб-сайтах, команда разработчиков обычно начинает с наброска на бумаге и потом строит цифровые макеты используя графические редакторы или веб-технологии.

Веб-команда часто включает в себя пару графических дизайнеров и дизайнера с опытом взаимодействия (user-experience (UX) designer). Графические дизайнеры, очевидно, работают вместе над визуализацией веб-сайта. UX дизайнеры играют более абстрактную роль, обращаясь к тому как пользователи будут пользоваться и взаимодействовать с веб-сайтом.

Выберите свои активы
--------------------

На данном этапе хорошо бы начать собирать контент, который в конечном итоге появится на вашей веб-странице.

### Текст

У вас должен быть текст, разбитый на заголовки и параграфы. Придерживайтесь этого правила.

### Цветовая схема

Чтобы выбрать цвет, перейдите в инструмент выбора цвета и выберите цвет, который вам нравится. Когда вы щёлкните по цвету, вы увидите странный код из шести цифр, например, `#660066`. Это называется *шестнадцатеричный код (hex(adecimal) code)* и он представляет ваш цвет. Скопируйте это код куда-нибудь прямо сейчас.

![](/ru/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like/screenshot_from_2014-11-03_17-40-49.png)

### Изображения

Чтобы выбрать изображение, перейдите в Google Картинки и найдите что-нибудь подходящее.

1. Когда вы найдёте изображение, которое хотели, щёлкните по нему.
2. Нажмите кнопку *В полном размере (View image)*.
3. На следующей странице, правым щелчком мыши на изображении (Ctrl + клик на Mac), выберите Сохранить изображение как... (*Save Image As...)*, и выберите место для сохранения вашего изображения. В качестве альтернативы, скопируйте адрес изображения из адресной строки браузера для последующего использования.

![](/ru/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like/screenshot_from_2014-11-04_15-09-21.png)

![](/ru/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like/screenshot_from_2014-11-04_15-20-48.png)

**Примечание:** Большинство изображений в Интернете, использованных в Google Картинках имеют авторские права. Для снижения вероятности нарушения авторских прав, используйте фильтр лицензии Google. Для этого: 1) кликните на **Инструменты поиска (Search tools)**, затем на 2) **Права на использование (Usage rights)**:

![](/ru/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like/screenshot_from_2014-11-04_14-27-45.png)

### Шрифт

Чтобы выбрать шрифт:

1. Перейдите на Google Fonts и прокрутите список вниз, пока не найдёте шрифт, который вам понравится. Вы также можете использовать элементы управления справа для дальнейшей фильтрации результатов.
2. Щёлкните по кнопке "плюс" рядом со шрифтом, который вы хотите выбрать.
3. Щёлкните по кнопке "\* Family Selected" на панели в нижней части страницы. ("\*" зависит от того, сколько шрифтов вы выбрали)
4. В всплывающем окне вы можете увидеть и скопировать строки кода, которые предоставляет Google, чтобы сохранить их позже в вашем текстовом редакторе.

![](/ru/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like/font1.png)

![](/ru/docs/Learn/Getting_started_with_the_web/What_will_your_website_look_like/font2.png)

* Назад
* Обзор: Getting started with the web
* Далее