Организация вёрстки

Олег Мохов

Как мы верстаем?

Типичная вёрстка

    templates/
    └── index.html
    └── index.css

Хороший CSS


a {
    color: red;
}

Типичный CSS


html,
body
{
    margin: 0;
    padding: 0;
    width: 100%;
    font-family: Arial, sans-serif;
}

header
{
    position: relative;
    border: 3px solid #000;
    width: 952px;
    height: 551px;
    margin: 18px auto 0;
}

/* далее ещё 400 строчек кода */

CSS по полочкам

/* сброс умолчаний */
/* общие стили */
/* шапка */
/* основная часть */
/* футер */
/* страница печати */
/* мобильная версия */
/* какие-то правки */
/* новая страница */
/* ещё какие-то правки */
/* стили новой шапки */

Большие проекты

Проблемы

  • При внесении правок результат не всегда предсказуем
  • Блоки зависят от окружения
  • DOM-lookup'ы
  • Смешение назначений

Большие проекты

  • Длительная поддержка кода
  • Много кода
  • Время на разработку ограничено
  • Большие команды
  • Единый стиль
  • Код должен быть качественным

Независимые блоки

  • Верстаем не макетами, а блоками
  • Блоки бывают атомарные или составные
  • HTML, CSS, JavaScript блока не зависит от других блоков

Достижение независимости

  • Модульность
  • Реиспользование
  • Общая предметная область
  • Разделение ответственности

Отказываемся от id

  • #id и .class идентичны по скорости наложнения на DOM-дерево (защита от «дурака»)
  • При этом работая с #id нельзя исключать, что когда-то элемент станет не уникальным
  • Нельзя полностью отказаться от использования id'шников, т.к они нужны для форм или якорей

Решение Яндекса: БЭМ

Блок

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

Вложенность

Свободное перемещение

Элемент

  • это часть блока, отвечающая за отдельную функцию
  • может находиться только в составе блока и не имеет смысла в отрыве от него

Модификатор

Модификатор

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

Витрина bem-components

Именование

Именование блоков

  • уникальное название, идентифицирующее блок
  • пробелы заменяются на дефисы
  • возможно использовать префиксы
  • это класс HTML-элемента

Именование элементов

  • уникальное название, идентифицирующее элемент внутри блока
  • название строится комбинированием имени блока и элемента (например блок__элемент)
  • это класс HTML-элемента

Именование модификаторов

  • уникальная пара ключ-значение, идентифицирующая определенное свойство и состояние блока/элемента
  • название строится добавлением к имени блока или элемент символа _ и названия (например, блок_модификатор или элемент_модификатор_значение)
  • это дополнительный класс HTML-элемента


     
    

Вёрстка на файловой системе

  • все сущности кладутся в отдельные директории
  • при использовании препроцессоров или постпроцессоров возможно ограничиваться только блоками
  • каждая технология в отдельный файл
    button/
    └── button.html
    └── button.css
    └── button.ie.css
    └── button.js
    └── _hovered/
        └── button_hovered.css
        └── button_hovered.png
    └── __icon/
        └── button__icon.css
        └── _color/
            └── button__icon_color_blue.css
            └── button__icon_color_red.css

Уровни переопределения

    common/
    └── header/
        └── header.css
        └── header.js
    desktop/
    └── header/
        └── header.css
    touch/
    └── header/
        └── header.css
        └── header.js

@import (common/header/header.css);
@import (desktop/header/header.css);
header.desktop.css


@import (common/header/header.css);
@import (touch/header/header.css);
header.touch.css

enb-make

https://github.com/enb-make/enb


({
    block: 'page'
})
index.bemjson.js


({
    mustDeps: [
        { block: 'header' }
    ],
    shouldDeps: [
        { block: 'button' },
        { block: 'icon', mods: ['warning', 'error'] }
    ]
})
page.deps.js

Шаблонизация


res.render('template', data);

Шаблонизация

Данные

HTML

Двухуровневая шаблонизация

Данные

БЭМ-дерево

HTML

BEMJSON


{
    block: 'page'
}



{
    block: 'page',
    mods: { type: 'main' }
}



{
    block: 'page',
    mods: { type: 'main' },
    content: {
        block: 'header'
    }
}


Императивное и декларативное программирование

Императивные языки программирования

«языки программирования, в которых описывается процесс вычисления в виде инструкций, изменяющих состояние программы (состояние памяти, состояние переменных...)»

Декларативные языки программирования

«языки высокого уровня, в которых не задается пошаговый алгоритм решения задачи ("как" решить задачу), а описывается, "что" требуется получить в качестве результата»

BH

https://github.com/bem/bh


bh.match('page', (ctx) => {
    ctx.tag('main');
});

bh.match('page', (ctx) => {
    ctx.content({
        elem: 'content',
        content: ctx.content()
    });
});

bh.match('page', (ctx) => {
    ctx.content([
        { elem: 'header' },
        ctx.content(),
        { elem: 'footer' }
    ]);
});


bh.match('page__footer', (ctx) => {
    ctx.content('THIS IS FOOOOTER!');
});

Уровни переопределения


bh.match('page', (ctx) => {
    ctx.content('CONTENT');
});
bh.match('page', (ctx) => {
    ctx.tag('div');
});

Инкапсуляция разметки

i-bem.js

1. Манипулируем не DOM, а BEM-объектами

2. Вместо событий реагируем на изменения модификаторов

3. Блок манипулирует только собой

4. Для связывания двух блоков используется не хождение вверх по DOM, а блоки-обёртки или каналы (глобальные события)

Ещё решения

  • OOCSS
  • SMACSS
  • Atomic CSS
  • MCSS
  • AMCSS
  • FUN

Способы организации CSS-кода

Polymer

Веб-компоненты

  • Templates
  • Custom Elements
  • Shadow DOM
  • Imports

Шаблонизаторы

  • Jade
  • Handlebars
  • Mustache
  • Django Templates (Python)
  • Smarty (PHP)
  • BEM
  • тыщи их...
  • HTML + JavaScript

Шаблоны в HTML



Шаблоны в HTML



Шаблоны в HTML



Templates

«Method of declaring a portion of reusable markup that is parsed but not rendered until cloned»
http://caniuse.com/#feat=template

Плюсы <templates>

  • Содержимое не обрабатывается и не загружается, пока шаблон не активирован
  • Содержимое не доступно с помощью querySelector и прочих функций
  • Шаблоны можно помещать куда угодно, в <head>, в <body> или даже внутрь <select>

Использование





var template = document.querySelector('#template1');
document.body.appendChild(template.cloneNode(true));

Поддержка браузерами

  • 2013 – 46%
  • 2015 – 63%
  • 2016 – 72%

CustomElements

Типичная вёрстка


...
...
...

Типичная вёрстка HTML5


...
...
...

Вёрстка с CustomElements


...
... ...

Создание


var Folders = Object.create(HTMLElement.prototype);
    Folders.createdCallback = function() {
        this.addEventListener('click', function(e) {
        alert('Thanks!');
    });
};
document.registerElement('folders', {prototype: Folders});

Lifecycle callbacks


createdCallback
// экземпляр элемента создан


enteredViewCallback
// экземпляр элемента добавлен в документ


leftViewCallback
// экземпляр элемента удалён из документа


attributeChangedCallback(attrName, oldVal, newVal)
// добавление/удаление/изменение аттрибута attrName

Поддержка браузерами

  • 2013 – 45%
  • 2015 – 45%
  • 2016 – 48%

Shadow DOM



     
    



Создание


var Shadow = Object.create(HTMLElement.prototype);
Shadow.createdCallback = function() {
    var shadow = this.createShadowRoot();
    shadow.innerHTML = "Ололо";
};

document.registerElement('my-element', {prototype: Shadow});

Shadow DOM + Templates + CustomElements

Всё вместе


var Shadow = Object.create(HTMLElement.prototype);
Shadow.createdCallback = function() {
    var shadow = this.createShadowRoot();
    var template = document
       .querySelector('template#myTemplate');
    shadow.appendChild(template.content);
};

document.registerElement('my-element', {
    prototype: Shadow
});

Поддержка браузерами

  • 2013 – 33%
  • 2015 – 46%
  • 2016 – 52%

Imports

Загрузка внешних ресурсов

  • <link rel="stylesheet"> для загрузки CSS
  • <script src> для загрузки скриптов
  • <img> для загрузки картинок
  • <audio> для загрузки аудио
  • <video> для загрузки видео
  • ??? для загрузки HTML

Загрузка HTML

  • <iframe> – всё своё (контекст, JS, стили), трудно взаимодействовать
  • AJAX – нужен JS, сложно кэшировать
  • <script type="text/html">

Imports


<head>
    
</head>

Особенности

  • Вёрстка и CSS глобальные
  • JavaScript глобальный, но поддерживает локальный скоуп через document.currentScript.ownerDocument
  • Кэширование вёрстки в браузере
  • Не блокируют загрузку страницы (async)

Поддержка браузерами

  • 2013 – ?
  • 2015 – 40%
  • 2016 – 48%

Ссылки