# CSS


# Главное

Используем pixel-perfect (opens new window), если заказчик предоставил макет;

# CSS - опасная сила

  • Все стили, подключаемые к странице, являются глобальными, и если этим злоупотреблять, то можно легко довести проект до состояния крайнего болезненного внедрения любого нового элемента или изменения старого. Это чаще всего происходит, когда создаются "общие" классы, потом крепятся к разным элементам на странице (банальный пример - шапка, у которой внутри форма поиска, которая есть на всех страницах с одинаковыми классами) и в итоге, когда по дизайну на разных страницах появляются небольшие отличия в шапке, плодятся классы, переписывающие свойства. И так почти для всех элементов каждой страницы. В итоге, меняя крайнюю правую ссылку, в сайдбаре на странице новостей вы можете поломать верстку всего сайдбара на какой-нибудь другой странице, например, странице личного кабинета.

  • Для того, чтобы такого не происходило, существуют разные методологии, например, БЭМ (прочитать про него обязательно хотя бы в общих чертах) или, например, rscss (opens new window). Для начала можете изучить небольшой подход (opens new window), который используют ребята из Isobar - он описан в рамках их стандарта и состоит из 7 небольших абзацев.

# Общие требования

  1. Отступы делать пробелами, один уровень - 2 пробела

  2. Использовать одинарные кавычки

  3. Перед началом работы обязательно четко знать все приоритеты селекторов и все 4 вида возможных отношений

    • Отношения элементов между собой могут быть;
      • div p – элементы p, являющиеся потомками div;
      • div > p – только непосредственные потомки;
      • div ~ p – правые соседи: все p на том же уровне вложенности, которые идут после div;
      • div + p – первый правый сосед: p на том же уровне вложенности, который идёт сразу после div(если есть);

    А про приоритеты можно почитать здесь (opens new window) и во многих других местах

  4. Не использовать @import внутри файлов

    • Имеется ввиду в конечном css файле, который получает клиент, не должно быть этих конструкций. Но если настроен WebPack и css-loader, который как раз этот случай обрабатывает, либо используются компилируемые аналоги, которые умеют вместо импортов подставлять непосредственное содержимое файлов, то импорты возможны, так как браузер все равно их не увидит и не получит никогда в реальной работе. Можно выделить пару причин, почему не стоит использовать @import:

      • Некоторые старые браузеры не поддерживают использование правила @import, и стили, которые мы подгружаем через это правило, будут потеряны;
      • @import блокирует параллельную загрузку стилей, а это означает, что браузер будет ожидать завершения загрузки импортированного файла, прежде чем начнёт обрабатывать остальную часть содержимого;

    Более подробно можно почитать здесь (opens new window)

  5. Все правила должны быть разбиты на разные файлы по страницам и блокам, каждый файл не должен быть длиннее чем 1000 строк

  6. Каждое CSS-правило должно быть на отдельной строке

    • Это очень поможет как минимум при просмотре diff-ов в системе контроля версий. Плюс это позволит избежать горизонтальной прокрутки
  7. Для всех интерактивных элементов (ссылки, кнопки, дропдауны, инпуты, селекты) дизайнер может прорисовать отдельные состояния и спрятать в ближайшем слое - всегда проверять для каждого элемента, есть ли отрисованное состояние

  8. Дизайнеры могут ошибаться - иногда надо делать запросы на изменение макета, если они нарисовали что-то переусложненное

    Например, в макете может быть отцентрированная по > вертикали относительно текста полоска.

    Пример.

    (эта картинка просто пример, не факт что тут как раз такая ошибка).

    Дизайнер может просто на глаз положить эту полоску на среднем уровне нижних букв текста, однако вы как верстальщики просто примените vertical-align: middle и увидите, что полоска выше/ниже уровня, который показал дизайн и Pixel Perfect явно это подсвечивает. Чаще всего не надо хардкодить какой-нибудь неочевидный отступ, просто чтобы идеально соответствовать макету - лучше сказать дизайнеру, что автоматом полоска ставится на другое место и её лучше там и оставить. А дизайнеру подучить типографику 😃

  9. Пытаться по максимуму делать сайт резиновым (ставьте ширину в процентах и выставляйте max-width, min-width) - прибегайте к фиксированной ширине элемента только если по макету от этого никак не уйти

    Если все-таки сайт должен быть с фиксированный шириной, обязательно проверяйте правую сторону каждой страницы на маленьких экранах. Очень частая ошибка новичков не все элементы делать с min-width и при горизонтально прокрутке получается такая штука:

    image

  10. При сборке проекта при отсутствии дизайна (на CSS-фреймворках, например) избегать "лепки"

    Если вы разрабатываете проект, у которого нет дизайна (при помощи CSS-фреймворков, например), то старайтесь избегать лепки. Правила те же, что и при написании программных модулей: старайтесь придать пространства между компонентами, ограничить их друг от друга визуально (осуществляется слабое зацепление), но вместе с этим, сами компоненты должны выглядеть самодостаточно, т.е. обладать всем необходимым, но без избыточности, функционалом, чтобы это можно было назвать полноценной компонентой (осуществляется сильная связанность)
    Хорошим детектором является тут БЭМ: старайтесь делать отступы между блоками минимум в два раза больше, чем внутри блока между элементами

  11. Все стили одного блока должны быть в одном месте и отсортированы по порядку их расположения на странице

    То есть, если есть шапка, а внутри нее поисковая форма, то в .css файлах нельзя сначала определить стили шапки, потом кучу стилей тела страницы, и потом стили поисковой формы шапки. Должны идти стили самой шапки и сразу под ней стили формы. При этом стили сайдбара не могут идти раньше стилей шапки, а стили футера идти раньше стилей сайдбара. Лучше вообще эти стили разнести по разным файлам.

  12. Футер страницы должен быть всегда прижат к низу страницы, даже если на странице мало контента (5 методов, как сделать это (opens new window))

  13. Имена

    • использовать lower-case-hyphenated (то есть не mySuperAwesomeElement и не my_super_awesome_element);
    • имена должны отображать смысл, а не описание стилей ("loading" а не "big-yellow-spinny-thing").

# Правила

  1. Не использовать !important

    В 99% случаях проблему можно решить грамотно используя БЭМ или CSS modules. В сложных редких случаях лучше увеличивать приоритет селекторов (например, когда надо стили библиотеки переопределить).

  2. Не использовать position absolute для стилизации, где это можно заменить другими подходами

  3. Не двигать элемент при помощи top, left, right или bottom при position relative, где это можно заменить другими подходами

    можно использовать сам position relative (без свойств top, left, right или bottom) для z-index и для того, чтобы указать потомкам элемента ориентир.

  4. Не использовать отрицательные margin

    На самом деле отрицательные margin - это валидные правила, они поддерживаются спецификацией CSS и их даже используют такие гиганты, как Bootstrap (посмотрите стили для .row).

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

  5. Избегать фиксирования высоты

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

    Когда это все-таки может быть нужно:

    • у элемента надо ручками подстроиться под высоту потомков, которых "убрали" из общего потока. "Убрать" из потока можно через position: absolute, например. Это может быть слайдер, у которого потомки крутятся как раз через позиционирование, а не смещение margin-ом;
    • по дизайну надо подстроить все элементы под определенную высоту, а контент, вылазящий за пределы, обрезать;
  6. Избегать фиксированных выравниваний

    Если блок должен быть отцентрирован (хоть по горизонтали, хоть по вертикали), то центрировать его динамически, а не фиксированным margin-top, например.

    Пример.

    здесь картинку, текст и полоску надо центрировать через vertical-align, а не top: 55px у полоски. Часто будут попадаться случаи более сложного центрирования - почти для каждого есть свой метод, надо их отдельно изучать.

    Статья про vertical-align (opens new window)

    Кратко:

    • Что нужно сделать чтобы центрировать по высоте элемент1 внутри элемента2:
      • Оба элемента должны быть любыми inline элементами;
      • У элемента2 должен быть указан font-size или line-height;
      • Элемент2 отцентрируется только в зависимости от font-size или line-height и не обратит внимания на height.
  7. Прописывать дефолтный цвет фона, цвет шрифта, font-size и семейство шрифта у html

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

  8. При тестировании обязательно посмотреть цвет :visited ссылки

    Браузер по умолчанию такие ссылки фиолетовыми делает, что чаще всего недопустимо в дизайне

  9. Добавлять элементу cursor: pointer, если он кликабелен и по умолчанию этого не поддерживает

  10. Тегу <img> стараться не выставлять одновременно width и height - тогда велика вероятность, что будут нарушены пропорции картинки. Если у картинки все-таки должны быть по макету четкие размеры, то сделать все через <div>, у которого картинка лежит на фоне и применить background-size: cover

  11. Удалять все неиспользуемые правила и правила, которые никак не влияют на отображение (например, дублирующие свойства по умолчанию или нулевые margin, если они и так не выставлены) - они могут в дальнейшем при правках внести неочевидное поведение и из-за них тяжело разбираться в проекте

  12. Слова, целиком состоящие из верхнего регистра, предпочтительно делать через стиль text-transform: uppercase, а не реальным вводом текста заглавными буквами

  13. При установке элементу outline: none обязательно выставить стили для :focus этому элементу

    outline очень полезен, когда юзер заполняет форму, использую клавишу tab для переключения между инпутами и кнопками. Если outline убрать, станет менее заметно (или незаметно вовсе для кнопок), какой элемент сейчас выбран.

  14. Все использования @media должны идти сразу после основных правил элемента, для того, чтобы можно было увидеть все правила и все возможные состояния элемента в одном экране без прокручивания

  15. Вендорные префиксы должны идти перед общим стилем

  • Стандартное правило всегда должно быть под специфичными правилами для каждого движка.

    Пример:

    .thing {
      -webkit-transition: all 100ms;
      -moz-transition: all 100ms;
      -o-transition: all 100ms;
      transition: all 100ms;
    }
    

# Шрифты

  1. Если клиент предоставил макеты с кастомными шрифтами - проверить, свободные ли они и если нет, то запросить купленные файлы шрифтов

  2. Шрифты должны быть в форматах .ttf, .woff, .svg, для их генерации воспользуйтесь Font Squirrel (opens new window)

  3. Если в проекте должна быть поддержка нелатинских символов (например, русских), проверьте, что в файле шрифта эти символы есть и при генерации через Font Squirrel (opens new window) обязательно укажите поддержку этих subsettings

  4. Разные начертания одного и того же шрифта подключайте под одним именем, но для разных font-weight и font-style

    @font-face {
      font-family: "Lato";
      src: url("../fonts/lato-regular.woff2") ...;
      /* Pay attention: font is regular and "..." replaced for other formats */
      font-weight: 400;
      font-style: normal;
    }
    @font-face {
      font-family: "Lato";
      src: url("../fonts/lato-italic.woff2") ...;
      /* Pay attention: font is regular italic and "..." replaced for other formats */
      font-weight: 400;
      font-style: italic;
    }
    @font-face {
      font-family: "Lato";
      src: url("../fonts/lato-light.woff2") ...;
      /* Pay attention: font is light and "..." replaced for other formats */
      font-weight: 300;
      font-style: normal;
    }
    @font-face {
      font-family: "Lato";
      src: url("../fonts/lato-lightitalic.woff2") ...;
      /* Pay attention: font is light italic and "..." replaced for other formats */
      font-weight: 300;
      font-style: italic;
    }
    
  5. На элементах без специфичных требований (не фиксированные пиксели и не относительно области экрана) использовать rem, изучить по максимуму чем отличаются px, pt, em, rem, vh, vw, vmin, vmax и их использовать в остальных случаях

  6. Всегда использовать как минимум один запасной шрифт и одно запасное семейство

    Их перечисляют через запятую в font-family, так что каждое использование font-family должно происходить по схеме font-family: Helvetica, Arial, sans-serif;

    • Здесь Helvetica - нестандартный подключаемый шрифт
    • Arial - стандартный шрифт, который используется почти на всех клиентах, он будет использоваться, если не удалось подключить Helvetica.
    • sans-serif - это семейство всех шрифтов без засечек (каковыми являются и Helvetica и Arial). Если даже Arial-а на компе нет, то поставится какой-то системный дефолтный шрифт без засечек. Выбор здесь надо делать из "serif," "sans-serif," или "monospace". Для шрифтов без засечек использовать по умолчанию Arial, для шрифтов с засечками Georgia (А не Times New Roman), а для моноширинных "Courier New"
  7. Названия шрифтов, содержащие пробелы, цифры или знаки пунктуации кроме дефисов заключать в кавычки

    Пример:

    .font-family: "Times New Roman", serif.;
    

# БЭМ

  1. Именование БЭМ-сущностей должно следовать следующим ограничениям:

    Таким образом, фразы на английском языке "$BLOCK_NAME [is] $MODIFIER_NAME", "$ELEMENT_NAME [is] $MODIFIER_NAME" или "$MODIFIER_NAME $BLOCK_NAME", "$MODIFIER_NAME $ELEMENT_NAME", "$ELEMENT_NAME WITH $MODIFIER_NAME $MODIFIER_KEY" должны быть синтаксически корректными словосочетаниями.

    Примеры корректных имён:

    • input_selectedselected input — выделенный инпут или input is selected — инпут выделен;
    • header_size_largeheader with large size — заголовок с большим размером.

    Примеры НЕкорректных имён:

    • form__save_small, для элемента save (который, например, вешается на кнопку) — small save — маленький сохранить, save small — сохранить маленький. Правильно будет употребить noun вместо verb и переименовать элемент в save-button; small save-button — маленькая кнопка сохранения;
    • input_focusinput focus — инпут сфокусировать или input is focus — инпут это фокус. Правильно будет употребить adjective вместо noun/verb и переименовать модификатор в focused; input_focusedfocused input — сфокусированный инпут;
    • row_errorrow error — ошибка строки или row is error — строка это ошибка; Правильно будет употребить adjectival phrase вместо nounи переименовать модификатор в with-error; row_with-errorrow with error — строка с ошибкой.

    Обоснование: мы сможем всегда однозначно и без лишних затрат энергии истолковать сущность элемента вёрстки и накладываемые на него свойства.

  2. Верстаем всегда по БЭМу, архитектура верстки должна быть компонентной

    Каждый блок должен лежать в своей папке. Папки блоков нельзя вкладывать друг в друга.

  3. Каждый компонент - отдельный блок из методологии БЭМ

  4. Каждый компонент должен быть иметь только явные зависимости, должен быть самодостаточен

    Все зависимости явно делать импортируя в начале компонента и вставляя в нужное место верстки Самодостаточность говорит о том, что каждый компонент должен внутри себя содержать все необходимое - всю верстку, все стили и все js-скрипты

    Ничего лишнего в компоненте быть не должно:

    • Не должно быть определения других блоков внутри этого блока;
    • Не должно быть стилей, которые бы влияли на другие блоки любым способом;
    • Не должно быть глобальных стилей (например, на все теги span);
    • компонента не должен влиять на DOM другого компонента (менять верстку крайне запрещено);
  5. Кастомизировать компоненты только через модификаторы, никаких примесей

    Почти в любом проекте возникает необходимость кастомизировать блоки. Например, есть вёрстка из 20 страниц. На 15 из этих страниц встречается хэдер, причём у 10 из них хэдер синего цвета, на трёх — серый, и на двух — прозрачный. Это значит, что компонента футер должна быть кастомизируемая, и осуществляются подобного рода кастомизации через добавление модификаторов к блоку, а не путём передачи отдельных классов со стилями.

    Предположим, что на фоне у промо страницы есть видео, и по дизайну нам нужно сделать прозрачный футер с светлым шрифтом (по умолчанию — тёмный).

    Плохо:

       .promo-page
         +header({classname: 'promo-page__header'})
    

    Хорошо:

       promo-page
         +header({theme: 'transparent', font: 'light'})
    

    При правильном подходе, естественно, надо будет научиться принимать эти два параметра, добавлять самому модификаторы к нужным классам и в CSS компонента прописывать правила для этих модификаторов.

    Это правило внедрено после болезненного опыта поддержки проекта средней сложности, и вот какие шишки набиты, используя примеси:

    • Нарушается инкапсуляция - использование внешних стилей внутри компоненты ведёт к проблемам в сопровождении в дальнейшем. Дело в том, что стили компоненты являются её составной частью, и не должны влиять на то, что её окружает. Так же справедливо обратное — внешние стили не должны влиять на компоненту. Иными словами, компонента должны быть самодостаточна. Прибегая к использованию внешних стилей мы создаём зависимость между двумя стилями - внешним, и внутренним. Каждому из стилей нужно знать, из чего состоит другой, чтобы глобальный стиль не поломал стиль компоненты. Это и есть нарушение инкапсуляции;
    • Если компонента используется на 20 разных страницах, то после изменения одного свойства в самом компоненте придется пройти по всем 20 страницам вручную и проверить что ничего не сломалось, потому что неизвестно какие могут быть стили навешаны в местах использования через кастомные классы и надо самому проверить все комбинации;
    • Примесь обычно вешается только на верхний уровень компонента, но иногда надо кастомизировать что нибудь внутреннее, тогда получается, надо уже две примеси передавать и принимать в компоненте, одна примесь для свойств всего блока, второй для какого-то элемента, а когда понадобится еще другой элемент кастомизировать, придется добавить третью примесь 😃 ;
    • Не получится четко составить список всех возможных состояний компоненты, а при подходе с модификаторами мы явно видим все возможные параметры на входе, и когда их число будет зашкаливать, можем пнуть дизайнера, что он слишком расфантазировался и пора бы переходить к единому стайлгайду;
  6. Для позиционирования блока в родительском контейнере тоже не надо использовать миксы - решается данная проблема путем добавления специальных контейнеров > Допустим, есть родительский блок, в нем дочерний блок, который нам и необходимо спозиционировать. Для позиционирования внутреннего блока создаем в родительском элемент-обертку и просто задаем свойства (margin, padding, position etc) этому контейнеру. Таким образом позиционирование блока у нас находится в файле родительского блока и никак не влияет на стили самого блока.

    Данный способ позволяет, не используя миксы, спозиционировать дочерний блок.

    Например, в хедере у нас находится обычная голубая кнопка, но именно тут, в шапке, она должна иметь отступ с левой стороны, для этого мы добавим div.header__button:

    div.header div.header__button +button({color:blue})
    

    А в стили хедера просто добавим:

    .header__button { margin-left: 3rem; }
    

# Возможные ошибки

В css есть несколько очень неочевидных правил, лучше изучить их на теории заранее, чем ломать голову часами, почему верстка едет.

  1. Верхний margin первого элемента смещает за собой всех родителей вниз.

    Допустим, желаемый вид должен быть таким

    image

    Чтобы заголовок имел отступ от верхней границы, мы добавляем ему margin: 20px 0 0; однако получаем такое поведение:

    image

    Несмотря на то, что маржин был поставлен только заголовку, все его родители тоже съехали вниз

    image

  2. Родители игнорируют всех детей, у которых установлены float: left или float: right

  3. Z-index свойство работает только для элементов, у которых значение position задано как absolute, fixed или relative

  4. А еще на z-index может повлиять ... opacity 😃 Почитать про это можно тут (opens new window)

  5. Height в процентах не работает, если высота родителя формируется от контента

    На https://learn.javascript.ru/ даже статья (opens new window) есть

  6. Все :hover эффекты проверять на мобильных устройствах внимательно в отдельном порядке, если проект поддерживает отображения на мобилах

    В разных браузерах ховеры ведут себя по-разному и это надо согласовывать с дизайнерами отдельно - иногда чтобы он заработал, надо, например, отдельный тап по элементу делать. Соответственно, чтобы сработал клик, надо тапнуть два раза, что тоже для некоторых элементов будет очень странно

  7. Псевдоэлементы (::after/before) не работают (opens new window) с self-closing tags (<img />, <input />, etc.)

  8. Не использовать простой :hover для отображения новых элементов, которые нелегко достигнуть мышкой

    Очень часто встречаются неудачные случаи с выпадающим меню, которое реализовано через псевдо-класс :hover, мало того, что это меню практически неработоспособно на мобильном устройстве, оно еще и доставляет немало головной боли пользователям десктопов. Решением, в данном случае, будет использование JavaScript.

    image

  9. В ряде браузеров (Chrome, Opera, Edge, Firefox 62-, Safari 10-) теги форм, такие как button, fieldset и legend, не могут стать flex-контейнерами

    Возможное решение - использовать элемент-обертку внутри тега (например, span class="button__text" внутри кнопки), и уже для него указать свойство display: flex:

    <button class="button">
      <span class="button__text">...</span>
    </button>
    
    .button__text {
      display: flex;
      ...;
    }
    

    Подробнее об этом и других багах, возникающих при работе с флексами, можно прочитать на Flexbugs (opens new window), перевод материала на русский здесь (opens new window).

    Для решения проблем с флексами можно воспользоваться неплохим postcss-плагином (opens new window), который автоматически фиксит некоторые баги в написании стилей для флексов.