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

Олег Мохов

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

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

    markup-task-1/
    └── 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'шники нужны для форм или якорей (разделение ответственности)

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

Блок

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

Вложенность

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

Элемент

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

Модификатор

Модификатор

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

Именование

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

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

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

  • это класс HTML-элемента
  • уникальное название, идентифицирующее элемент внутри блока
  • название строится комбинированием имени блока и элемента через __
  • <div class="navigation__link">

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

  • это дополнительный класс HTML-элемента
  • пара ключ-значение, т.е свойство и/или состояние блока/элемента
  • название строится добавлением _ключа_значения к имени блока или блока__элемента
  • в булевых можно опускать значение
  • <div class="navigation_type_buttons">
  • <div class="navigation__link_hidden">


     
    

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

  • все сущности кладутся в отдельные директории
  • при использовании препроцессоров или постпроцессоров возможно ограничиваться только блоками
  • каждая технология в отдельный файл
    button/
    └── button.html
    └── button.css
    └── button.ie.css
    └── button.js
    └── button.md
    └── _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

Технология deps.js


({
    mustDeps: [
        { block: 'jquery' }
    ],
    shouldDeps: [
        { block: 'logo' },
        { block: 'tabs', mods: { type: 'bookmarks' } },
        { block: 'search' },
        { block: 'auth' }
    ]
})
header.deps.js

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


res.render('template', data);

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

Данные

HTML

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

Данные

БЭМ-дерево

HTML

BEMJSON


{
    block: 'header'
}



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



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


BH

https://github.com/bem/bh


npm i --save-dev bh

var bh = new (require('bh').BH);

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

bh.apply({block: 'header'});



var bh = new (require('bh').BH);

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

bh.apply({block: 'header'});


ctx

  • ctx – инстанс класса Ctx
  • Содержит методы для модификации DOM-дерева и работы с шаблоном

json

  • Второй аргумент callback у bh.match
  • json - текущий BEMJSON

header content


{
    block: 'header',
    content: 'header content',
    mark: 'alone'
}


bh.match('header', (ctx, json) => {
    ctx.content({
        elem: 'content',
        attrs: { title: json.mark },
        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('main');
});

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


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

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

Ещё решения

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

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

Как не писать CSS?

Препроцессоры

Код

Препроцессор

CSS


.menu {
    display: inline-block;
}

.menu__item {
    background: #d0881d;
}

.menu__item:hover {
    background: #ebb96f;
}

.menu__arrow {
    background: #d0881d;
}


            

$bg_color = #d0881d;

.menu {
    display: inline-block;
}

menu__item {
    background: $bg_color;
}

.menu__item:hover {
    background: lighten($bg_color, 40%);
}

.menu__arrow {
    background: $bg_color;
}
            

.menu {
    display: inline-block;
}

.menu__item {
    background: #d0881d;
}

.menu__item:hover {
    background: #ebb96f;
}

.menu__arrow {
    background: #d0881d;
}


            


$bg_color = #d0881d;

.menu {
    display: inline-block;
}

menu__item {
    background: $bg_color;
}

.menu__item:hover {
    background: lighten($bg_color, 40%);
}

.menu__arrow {
    background: $bg_color;
}
 
            

.menu {
    display: inline-block;
}

.menu__item {
    background: #d0881d;
}

.menu__item:hover {
    background: #ebb96f;
}

.menu__arrow {
    background: #d0881d;
}
            


$bg_color = #d0881d;

.menu {
    display: inline-block;

    &__item {
        background: $bg_color;

        &:hover {
            background: lighten($bg_color, 40%);
        }
    }

    &__arrow {
        background: $bg_color;
    }
}
                
            

.menu {
    display: inline-block;
}

.menu__item {
    background: #d0881d;
}

.menu__item:hover {
    background: #ebb96f;
}

.menu__arrow {
    background: #d0881d;
}
            


$bg_color = #d0881d;

.menu {
    display: inline-block;

    &__item {
        background: $bg_color;

        &:hover {
            background: lighten($bg_color, 40%);
        }
    }

    &__arrow {
        background: $bg_color;
    }
}
                
            

Препроцессоры

Less

Sass

Stylus

Установка


npm install stylus
        

Компиляция


stylus index.styl --out ./css/index.css
        

stylus --watch index.styl
        

Переменные


$font = 14px Helvetica, sans-serif;
box_width = 30%;
box_height = 300px;

.block {
    font: $font;
    width: box_width;
    height: box_height;
}
            

.block {
    font: 14px Helvetica, sans-serif;
    width: 30%;
    height: 300px;
}




            

Операторы


$box_width = 300px;
$box_height = $box_width * 2;

.box {
    width: $box_width;
    height: $box_height;
}
            

.box {
    width: 300px;
    height: 600px;
}



            

Вложенность


.header {
    .title {
        font-size: 20px;
    }

    .link {
        color: green;
        text-decoration: none;

        &:hover {
            text-decoration: underline;
        }
    }
}
            

.header .title {
    font-size: 20px;
}

.header .link {
    color: #008000;
    text-decoration: none;
}

.header .link:hover {
    text-decoration: underline;
}


            

Вложенность


.header {
    &__title {
        font-size: 20px;
    }

    &__link {
        color: green;
        text-decoration: none;

        &:hover {
            text-decoration: underline;
        }
    }
}
            

.header__title {
    font-size: 20px;
}

.header__link {
    color: #008000;
    text-decoration: none;
}

.header__link:hover {
    text-decoration: underline;
}


            

Массивы и циклы


$col_list = 1 2 3 4;
            for $col in $col_list {
    td:nth-child({$col}) {
        width: 10% * $col;
    }
}
            for $col in (1..4) {...}
                            

td:nth-child(1) {
    width: 10%;
}

td:nth-child(2) {
    width: 20%;
}

td:nth-child(3) {
    width: 30%;
}

td:nth-child(4) {
    width: 40%;
}
            

Hashes


$cats = {
    cat_1: './images/cat1.jpg',
    cat_2: './images/cat2.jpg'
}
            
$cats.cat_1 = './images/cat1_new.jpg';
$cats['cat_3'] = './images/cat3.jpg';
            
for $name, $bg_img in $cats {
    #img-{$name} {
        background: url($bg_img);
    }
}
            

#img-cat1 {
    background: url("./images/cat1_new.jpg");
}

#img-cat2 {
    background: url("./images/cat2.jpg");
}

#img-cat3 {
    background: url("./images/cat3.jpg");
}




            

Условные операторы


$theme = 'day';

.sky {
    if $theme == 'day' {
        background: blue;
        background-image: url(sun.png);
    } else {
        background: black;
        background-image: url(stars.png);
    }
}
            

.sky {
    background: #00f;
    background-image: url(sun.png);
}
            

import


@import 'theme'
        

// theme_day.styl

$bg_color = blue;
$bg_img = sun.png;
            

// main.styl

@import 'theme_day'

.sky {
    background: $bg_color;
    background-image: url($bg_img);
}
            

/* main.css */

.sky {
    background: #00f;
    background-image: url(sun.png);
}
            

Миксины


set_bg_color($theme) {
    if $theme == 'day' {
        background: blue;
        background-image: url(sun.png);
    } else {
        background: black;
        background-image: url(stars.png);
    }
}

.sky {
    set_bg_color('night');
}
            

.sky {
    background: #000;
    background-image: url(stars.png);
}
            

nib


npm install nib
        
@import 'nib'
        
@import 'nib/gradients'
@import 'nib/buttons'
        

Gradient


body {
    background linear-gradient(bottom left, 80% white, blue, red)
}
            

body {
    background: -webkit-linear-gradient(bottom left, #fff 80%, #00f, #f00);
    background: -moz-linear-gradient(bottom left, #fff 80%, #00f, #f00);
    background: -o-linear-gradient(bottom left, #fff 80%, #00f, #f00);
    background: -ms-linear-gradient(bottom left, #fff 80%, #00f, #f00);
    background: linear-gradient(to top right, #fff 80%, #00f, #f00);
}
            

Position


#back-to-top {
    fixed bottom 10px right 5px
}
        

#back-to-top {
    position: fixed;
    bottom: 10px;
    right: 5px;
}
        

Transparent Mixins


.animate-item {
    animation-delay 1s;
    animation-duration 1s;
}
        

.animate-item {
    -webkit-animation-delay: 1s;
    -moz-animation-delay: 1s;
    -o-animation-delay: 1s;
    animation-delay: 1s;
    -webkit-animation-duration: 1s;
    -moz-animation-duration: 1s;
    -o-animation-duration: 1s;
    animation-duration: 1s;
}
        

Responsive Images


#logo {
    image '/images/logo.main.png'
}
        

#logo {
    background-image: url(/images/logo.main.png);
}

@media all and (-webkit-min-device-pixel-ratio: 1.5) {
    #logo {
        background-image: url(/images/logo.main@2x.png);
        background-size: auto auto;
    }
}
        

Комментарии


// Очень содержательный комментарий
        

/*
Длинный содержательный комментарий
*/
        

Debug

source maps

Создание


stylus index.styl -m
        

{
    "version": 3,
    "sources": ["index.styl"],
    "names": [],
    "mappings": "AA2BQ;EACI,YAAwB,kCAAxB;EACA,iBAAiB,KAAjB...
    "file": "index.css"
}
        

DevTools

WebStorm

Постпроцессоры

CSS

Парсер

АSТ

Плагины

toString

CSS

PostCSS


npm install postcss
        

npm install autoprefixer
        

Запуск


postcss --use autoprefixer -c options.json -o main.css css/*.css
        

options.json


{
    "autoprefixer": {
        "browsers": "> 5%"
    }
}
        

{
    "autoprefixer": {
        "browsers": "Firefox > 20, last 2 Chrome versions"
    }
}
        

Autoprefixer


.box {
    transition: transform 1s
}
        

.box {
    -webkit-transition: -webkit-transform 1s;
    transition: -ms-transform 1s;
    transition: transform 1s
}
        

Color short


.box {
    border-bottom: 1px solid rgb(200);
    background: #20;
    color: #f;
    box-shadow: 0 1px 5px rgba(0, 0.5);
}
            

.box {
    border-bottom: 1px solid rgb(200, 200, 200);
    background: #202020;
    color: #fff;
    box-shadow: 0 1px 5px rgba(0, 0, 0, 0.5);
}
            

Size


.one {
    size: 20px 10px;
}

.two {
    size: 10px;
}
        

.one {
    width: 20px;
    height: 10px;
}

.two {
    width: 10px;
    height: 10px;
}
        

postcss-sprites


.comment {
    background: url(images/sprite/ico-comment.png) no-repeat 0 0;
}

.bubble {
    background: url(images/sprite/ico-bubble.png) no-repeat 0 0;
}
        

.comment {
    background-image: url(images/sprite.png);
    background-position: 0 0;
}

.bubble {
    background-image: url(images/sprite.png);
    background-position: 0 -50px;
}
        

Font Magician


body {
    font-family: "Alice";
}
        

@font-face {
    font-family: "Alice";
    font-style: normal;
    font-weight: 400;
    src: local("Alice"), local("Alice-Regular"),
        url("http://fonts.gstatic.com/s/alice/v7/sZyKh5NKrCk1xkCk_F1S8A.eot?#") format("eot"),
        url("http://fonts.gstatic.com/s/alice/v7/l5RFQT5MQiajQkFxjDLySg.woff2") format("woff2"),
        url("http://fonts.gstatic.com/s/alice/v7/_H4kMcdhHr0B8RDaQcqpTA.woff")  format("woff"),
        url("http://fonts.gstatic.com/s/alice/v7/acf9XsUhgp1k2j79ATk2cw.ttf")   format("truetype")
}
body {
    font-family: "Alice";
}
        

PostCSS BEM


@b nav {
    @e item {
        display: inline-block;
    }
    @m placement_header {
        background-color: red;
    }
}
        

.nav__item {
    display: inline-block;
}

.nav_placement_header {
    background-color: red;
}
        

CSSNano


h1::before, h1:before {
    margin: 10px 20px 10px 20px;
    color: #ff0000;
    -webkit-border-radius: 16px;
    border-radius: 16px;
    font-weight: normal;
    font-weight: normal;
}
/* invalid placement */
@charset "utf-8";
        

@charset "utf-8";h1:before{margin:10px 20px;
color:red;border-radius:1pc;font-weight:400}
            

CSSNext

Any-Link


nav :any-link > span {
    background-color: yellow;
}

        

nav :link > span,
nav :visited > span {
    background-color: yellow;
}
        

Matches


.rating-star:matches(:first-child, .special) {
    color: red;
}
        

.rating-star:first-child, .rating-star.special {
    color: red;
}
        

postcss-color-rgba-fallback


.rgbaFallback {
    background: rgba(0,0,0,0.5);
}
        

.rgbaFallback {
    background: #000000;
    background: rgba(0,0,0,0.5);
}
        

postcss-opacity


.opacityFallback {
    opacity: 0.5;
}
        

.opacityFallback {
    opacity: 0.5;
    -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";
}
        

node-pixrem


body {
    font-size: 16px;
}

.remFallback {
    height: 10rem;
    font: 2rem Arial;
}
        

body {
    font-size: 16px;
}

.remFallback {
    height: 160px;
    height: 10rem;
    font: 32px Arial;
    font: 2rem Arial;
}
        

Ссылки