193 lines
11 KiB
Markdown
193 lines
11 KiB
Markdown
# Вёрстка графических компонентов
|
||
|
||
При создании графических компонент ("виджетов") в первую очередь придумывается их HTML/CSS-структура.
|
||
|
||
Как будет выглядеть виджет в обычном состоянии? Как будет меняться в процессе взаимодействия с посетителем?
|
||
|
||
Чтобы разработка виджета была удобной, при вёрстке полезно соблюдать несколько простых, но очень важных соглашений.
|
||
|
||
[cut]
|
||
|
||
## Семантическая вёрстка
|
||
|
||
**HTML-разметка и названия CSS-классов должны отражать не оформление, а смысл.**
|
||
|
||
Например, сообщение об ошибке можно сверстать так:
|
||
|
||
```html
|
||
<div *!*style="color:red; border: 1px solid red"*/!*>
|
||
Плохая вёрстка сообщения об ошибке: атрибут style!
|
||
</div>
|
||
```
|
||
|
||
...Или так:
|
||
|
||
```html
|
||
<div *!*class="red red-border"*/!*>
|
||
Плохая вёрстка сообщения об ошибке: несемантический class!
|
||
</div>
|
||
```
|
||
|
||
В обоих случаях вёрстка не является семантической. В первом случае -- стиль, а во втором -- класс содержат информацию об *оформлении*.
|
||
|
||
**При семантической вёрстке классы описывают смысл ("что это?" -- меню, кнопка...) и состояние (открыто, закрыто, отключено...) компонента.**
|
||
|
||
Например:
|
||
|
||
```html
|
||
<div *!*class="error"*/!*>
|
||
Сообщение об ошибке (error), правильная вёрстка!
|
||
</div>
|
||
```
|
||
|
||
У предупреждения будет класс `message` и так далее, по смыслу.
|
||
|
||
```html
|
||
<div *!*class="warning"*/!*>
|
||
Предупреждение (warning), правильная вёрстка!
|
||
</div>
|
||
```
|
||
|
||
**Семантическая верстка упрощает поддержку и развитие CSS, упрощает взаимодействие между членами команды.**
|
||
|
||
Такая верстка удобна для организации JS-кода. В коде мы просто ставим нужный класс, остальное делает CSS.
|
||
|
||
## Состояние виджета -- класс на элементе
|
||
|
||
Зачастую компонент может иметь несколько состояний. Например, меню может быть открыто или закрыто.
|
||
|
||
**Состояние должно добавляться CSS-классом не на тот элемент, который нужно скрыть/показать/..., а на тот, к которому оно "по смыслу" относится, обычно -- на корневой элемент.**
|
||
|
||
Например, меню в закрытом состоянии скрывает свой список элементов. Класс `open` нужно добавлять не к списку опций `<ul>`, который скрывается-показывается, а к *корневому элементу* виджета, поскольку это состояние касается всего меню:
|
||
|
||
```html
|
||
<div class="menu *!*open*/!*">
|
||
<span class="title">Заголовок меню</span>
|
||
<ul>
|
||
<li>Список элементов</li>
|
||
</ul>
|
||
</div>
|
||
```
|
||
|
||
Ещё пример -- индикатор загрузки:
|
||
|
||
```html
|
||
<div class="loader">
|
||
<span class="progress">Тут показывается прогресс</span>
|
||
</div>
|
||
```
|
||
|
||
Состояние индикатора может быть "в процессе" (loading) или "загрузка завершена" (complete). С точки зрения оформления оно может влиять только на показ внутреннего `span`, но ставить его нужно всё равно на внешний элемент, ведь это -- состояние всего компонента:
|
||
|
||
```html
|
||
<div class="loader *!*loading*/!*">
|
||
<span class="progress">Тут показывается прогресс</span>
|
||
</div>
|
||
```
|
||
|
||
Из примеров выше можно подумать, что классы, описывающие состояние, всегда ставятся на корневой элемент. Но это не так.
|
||
|
||
Возможно и такое, что состояние относится к внутреннему элементу. Например, для дерева состояние открыт/закрыт относится к узлу, соответственно, класс должен быть на узле.
|
||
|
||
Например:
|
||
|
||
```html
|
||
<ul class="tree">
|
||
<li class="*!*closed*/!*">
|
||
Закрытый узел дерева
|
||
</li>
|
||
<li class="*!*open*/!*">
|
||
Открытый узел дерева
|
||
</li>
|
||
...
|
||
</ul>
|
||
```
|
||
|
||
На практике, даже если в начале разработки поставить класс не там -- то, при правильном понимании CSS, рано или поздно он всё равно переместится куда надо, поскольку стилизация открытого/закрытого меню касается также и заголовка.
|
||
|
||
Но оптимальнее -- сразу ставить его на правильное место.
|
||
|
||
## Префиксы у классов
|
||
|
||
Посмотрите, пожалуйста, вёрстку для виджета диалогового окна.
|
||
|
||
```html
|
||
<div class="dialog">
|
||
<h2 class="title">Заголовок</h2>
|
||
<div class="content">
|
||
Содержимое. Имена классов в этой вёрстке опасны.
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.dialog .title { стиль заголовка }
|
||
.dialog .content { стиль содержимого окна }
|
||
</style>
|
||
```
|
||
|
||
В этой вёрстке есть серьёзная проблема, которая появится, если в содержимом окна будет элемент с классом `.title`:
|
||
|
||
```html
|
||
<div class="dialog">
|
||
<h1 class="*!*title*/!*">Заголовок</h1>
|
||
<div class="content">
|
||
|
||
*!*
|
||
<h2 class="*!*title*/!*">Привет!</h2>
|
||
... текст диалога ...
|
||
*/!*
|
||
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
Такое вполне возможно, ведь диалоговое окно может иметь любое содержимое.
|
||
|
||
**В этом случае CSS-правило `.dialog .title` будет применено и к `<h2 class="title">` в содержимом с непредсказуемыми последствиями.**
|
||
|
||
Конечно, можно попытаться бороться с этим. Например, нейтрализовать его действие, добавив дополнительное правило `.dialog .content .title`, но это скорее "заплатка", нежели полноценное решение проблемы.
|
||
|
||
Ещё один вариант -- жёстко задать вложенность. А именно, использовать класс `.dialog > .title`. Это сработает в данном конкретном примере, но как быть в тех местах, где нужен более глубокий потомок?
|
||
|
||
**Чтобы избежать возможных проблем, все классы внутри виджета начинают с его имени.**
|
||
|
||
Подходящий вариант:
|
||
|
||
```html
|
||
<div class="*!*dialog*/!*">
|
||
<h1 class="*!*dialog-title*/!*">Заголовок</h1>
|
||
<div class="*!*dialog-content*/!*">Содержимое</div>
|
||
</div>
|
||
|
||
<style>
|
||
.dialog-title { стиль загловка }
|
||
.dialog-content { стиль содержимого окна }
|
||
</style>
|
||
```
|
||
|
||
В этом случае внутрь `.dialog-content` можно смело помещать другие компоненты со своими классами `..-title`, `..-content` и т.п.
|
||
|
||
Кроме всего прочего, обработка такого CSS будет чуть-чуть быстрее ;) Так как один класс вместо каскада.
|
||
|
||
[smart header="Когда префиксы не нужны?"]
|
||
Префиксы делают названия классов длиннее, поэтому иногда не хочется их ставить.
|
||
|
||
Без них можно обойтись в тех случаях, когда внутри элемента заведомо не будет произвольного HTML и других компонент. То есть когда конфликты заведомо исключены.
|
||
|
||
С другой стороны, требования имеют свойство расти. Компоненты зачастую вставляются туда, где их не предполагалось. Ваш виджет, написанный для одной задачи или проекта, может быть потом использован совсем в другом месте, где потребуются вложенные компоненты. И тогда заранее предусмотренные префиксы сослужат хорошую службу.
|
||
[/smart]
|
||
|
||
## Итого
|
||
|
||
<ul>
|
||
<li>Вёрстка должна быть семантической, использовать соответствующие смыслу информации теги и классы.</li>
|
||
<li>Класс, описывающий состояние всего компонента, нужно ставить на его корневом элементе, а не на том, который нужно "украсить" в этом состоянии. Если состояние относится не ко всему компоненту, а к его части -- то на соответствующем "по смыслу" DOM-узле.</li>
|
||
<li>Классы внутри компонента должны начинаться с префикса -- имени компонента.
|
||
|
||
Это не всегда строго необходимо, но позволяет избежать проблем в случаях, когда компонент может содержать произвольный DOM, как например диалоговое окно с произвольным HTML-текстом.
|
||
|
||
Использование `.dialog-title` вместо `.dialog .title` гарантирует, что CSS не применится по ошибке к какому-нибудь другому `.title` внутри диалога.
|
||
</li>
|
||
</ul>
|
||
|
||
|