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