en.javascript.info/02-ui/05-widgets/03-widgets-markup/article.md
Ilya Kantor f301cb744d init
2014-10-26 22:10:13 +03:00

11 KiB
Raw Blame History

Вёрстка графических компонентов

При создании графических компонент ("виджетов") в первую очередь придумывается их 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 внутри диалога.