Организация CSS-кода
23-11-2019Reading time ~ 8 min.CSS, HTML, LESS, SASS 5718
Как правильно сгенерировать css-файлы, чтобы они положительно влияли на загрузку и при этом с ними удобно было бы работать? Такой вопрос неизбежно задаёт себе каждый верстальщик. Когда мы делаем какой-то свой личный проект, проблема не особо актуальна, но если речь идёт о чем-то публичном, то она обостряется.
За время работы с UniCSS и Berry я постепенно смог сформулировать основные идеи, которые позволяют достаточно неплохо решить данную проблему. Особенно она будет интересна тем, кто использует сторонние css-фреймворки, вроде Bootstrap'а.
Загрузка с точки зрения браузера
Вначале нужно понять что именно мы хотим получить в итоге. Очень важный момент — это то, что в HTML 5 теперь совершенно легально можно загружать css-файлы в конце BODY — это так называемая отложенная или lazy-загрузка.
Таким образом весь css-код делится на две части: обязательная, та которая загружается в HEAD и та, которая может быть подгружена в конце BODY.
Браузер загружает файлы сразу после того, как получит html-код страницы (и это определяется его внутренними алгоритмами). Если какой-то файл уже загружен, то он может взяться из кэша, а значит порядок реальной загрузки не всегда соответствует тому, как он указан в html-коде. Но браузер применяет css-стили только в указанном порядке. То есть когда мы говорим о том, что размер файлов критичен, поскольку влияет на скорость загрузки, то это не всегда соответствует действительности. Может получиться даже так, что css-файл из BODY будет загружен раньше, чем в секции HEAD.
Поэтому размер css-файлов хоть и влияет на общую загрузку страницы, но не так сильно, как это может показаться изначально. Если на сервере включено gzip-сжатие трафика, то в реальности передаётся примерно в 4-5 раз меньше для текстовых файлов. Например, если размер css-файла 50Кб, то реальный трафик будет где-то 10Кб и это очень немного даже для мобильных устройств.
Так что если мы видим на сайте «тормоза», то скорее всего дело здесь не в CSS, а скорее в JS, который блокирует отрисовку и выполнение кода до полной загрузки всех js-файлов.
И именно по этой причине необходимо выделять часть стилей в секцию HEAD, чтобы на момент начальной загрузки посетитель видел страницу в более-менее нормальном варианте.
CSS-каталоги
Всё это приводит нас к простой структуре каталога «css»:
- lazy — каталог, где файлы будут подключаться в конце BODY
- style.css — файл, который будет в HEAD
- прочие каталоги, из которых подключение ситуативное, например в компонентах или блоках.
Файл для секции HEAD по возможности должен быть один, поскольку это сократит количество http-соединений. Тем более, что файл будет небольшой и нет смысла его разделять.
Lazy-файлы лучше всего подключать в определенной последовательности, поскольку это гарантирует нормальную «перезаписываемость» стилей. Покажу пример:
lazy/ 1-bootstrap.css 2-template.css 3-colors.css 4-fontawesome.css animate.css cssgram.css
Поскольку все файлы компилируются Sass, то у нас нет проблем скомбинировать их так, как нужно.
Sass-каталоги
На всякий случай отмечу, что Sass не компилирует файлы, начинающиеся с символа подчеркивания «_». Это позволяет организовывать «служебные» каталоги, которые не появятся в css-каталоге.
При организации файлов следует учитывать, что мы можем использовать сторонние разработки. Например я использую тот же Bootstrap, свой Berry CSS, animate.css и CSSgram. При этом есть ещё каталог для css-профилей MaxSite CMS и файлы для разных менюшек.
Лучшим вариантом будет размещать все файлы по своим каталогам. Приведу пример:
sass/ berry/ - Berry CSS bootstrap/ - Bootstrap fontawesome/ - иконки FA fonts/ - подключение шрифтов lazy/ - то что будет в css/lazy menu/ - менюшки profiles/ - css-профили MaxSite CMS template/ - стили шаблона typography/ - типографика style.scss - файл для HEAD css/style.css
Каждый (свой) каталог содержит только scss-файлы без деления на подкаталоги.
Особенности использования сторонних библиотек
Некоторые библиотеки поставляются с готовым css-файлом, поэтому его можно сразу разместить в lazy-каталоге. Но, если это css-фреймворк, то возникает серьёзная проблема нарушения последовательности подключения.
Например мы используем Bootstrap. Он содержит всю типографику, сбросы и т.д. Логично его подключать первым. Но часть стилей мы должны переписать, например отступы заголовков (в Bootstrap'е верхний margin фиксированный). Конечно, мы можем поправить его исходный scss-файл, но это приводит к тому, что при обновлении потребуется ещё раз внести правку. Поэтому лучшим вариантом будет перезагрузка стилей своими.
У нас вырисовывается последовательность: вначале Bootstrap, после свои стили. Поскольку Bootstrap достаточно большой файл, то наверное его есть смысл разместить как lazy, а это в свою очередь заставляет и все остальные css-файлы загружать уже после него.
Совершенно очевидно, что такое решение приводит к проблеме начального отображения страницы — она будет жутко «куралеситься», пока не загрузятся все css-файлы.
Получается, что единственным вариантом будет размещение базовой части Bootstrap'а в HEAD, а все его второстепенные части уже в конце BODY. К счастью фреймворк написан на Sass, а значит есть возможность его скомпилировать частями.
Полезность утилитарных классов
Я использую Berry CSS, но это совершенно не принципиально. Обычно такие классы (они ещё называются Atomic CSS) загружают в конце остальных. Но на самом деле они должны загружаться первыми, поскольку используются при верстке большинства блоков. Простой пример.
Пусть есть утилитарные классы .mar20 {margin: 20px}
и .w50 {width: 50%}
, которые используются в шапке, подвале и какие-то блоки на главной. Если мы загрузим эти классы в самом конце, то все эти блоки будут изначально отображены некорректно. Но, если это будет начальная загрузка, то посетитель сразу увидит корректный вывод.
Утилитарные классы достаточно небольшие по объему, а значит их можно смело разместить в секции HEAD.
Секция HEAD
Что у нас получается в итоге. Первыми используем утилитарные классы. Поскольку они не конфликтуют по именам, то проблем с ними не будет. Дальше в ход идёт css-фреймворк, но только основная часть: сбросы и типографика. И в конце мы подключаем свой файл, который изменяет (если нужно) стили фреймворка.
Всё это размещается в style.scss. Приведу свой вариант одного из шаблонов с использованием Berry CSS и Bootstrap.
// загружаем шрифты @import "typography/fonts"; // Berry CSS без colors @import 'berry/variables/variables'; @import 'berry/block'; @import 'berry/border'; @import 'berry/flex'; @import 'berry/height'; @import 'berry/layout'; @import 'berry/opacity'; @import 'berry/padding-margin'; @import 'berry/position'; @import 'berry/rounded'; @import 'berry/text'; @import 'berry/visible'; @import 'berry/width'; @import 'berry/links'; @import 'berry/other'; // часть Bootstrap @import "bootstrap/functions"; @import "bootstrap/my_variables"; // файл своих переменных @import "bootstrap/variables"; @import "bootstrap/mixins"; @import "bootstrap/root"; @import "bootstrap/reboot"; @import "bootstrap/type"; // то, что хотим переписать от bootstrap/reboot и bootstrap/type @import 'typography/base';
В итоге получается 45Кб css-файл, что даёт всего 8Кб реального трафика для секции HEAD.
О шрифтах
Сейчас подключать шрифты нужно в секции HEAD. Это большая головная боль, которая благополучно разрешилась за счёт поддержки font-display: swap;
. Шрифт — это критически важный элемент дизайна, поэтому будет лучше, если браузер узнает о нём на самых ранних этапах загрузки страницы. Если размер шрифта небольшой (особенно если это woff2), то браузер успевает загрузить его уже к началу просмотра, что позволяет избежать неприятного мелькания.
Lazy Bootstrap
Bootstrap должен загрузиться первым, чтобы можно было бы переписать его стили после. Поэтому используется файл lazy/1-bootstrap.scss:
@import "bootstrap/functions"; @import "bootstrap/my_variables"; // my variables @import "bootstrap/variables"; @import "bootstrap/mixins"; // а это уже загружено раньше // @import "bootstrap/root"; // @import "bootstrap/reboot"; // @import "bootstrap/type"; @import "bootstrap/images"; @import "bootstrap/code"; @import "bootstrap/grid"; ... и т.д.
Lazy Template
Дальше мы загружаем lazy/2-template.scss. Именно этот файл и отвечает за нашу вёрстку. Все файлы нашего шаблона делятся на два каталога: template и typography.
В каталоге typography размещаются те стили, которые не имеют привязки к специфичным css-классам. Это верстка блоков (PRE, CODE и т.д.), кнопки BUTTON, элементы форм (INPUT, TEXTAREA, SELECT и т.д.), иконки, изображения, списки, линии, эффекты и т.д. Формально все эти стили можно скомпилировать совершенно отдельно без каких-либо миксинов и прочих зависимостей. Единственная связь может быть с перемененными цветов (от Berry CSS).
И опять же, поскольку у нас уже загружен Bootstrap, то есть смысл в своей типографике переопределять только то, что реально нужно.
Каталог template содержит всё то, что имеет непосредственное отношение к шаблону. Здесь уже идёт привязка к классам MaxSite CMS, виджетам, блокам, плагинам и т.д. Вот примерно так выглядит lazy/2-template.scss:
// переменные Berry CSS могут использоваться в остальных файлах @import 'berry/variables/variables'; // общие переменные @import 'berry/variables/all-colors'; // все цвета // аналогично могут использоваться переменные FA @import 'fontawesome/variables'; @import 'fontawesome/mixin'; // @import 'typography/base'; - этот файл уже загружен в HEAD // типографика @import 'typography/blocks'; @import 'typography/button'; @import 'typography/forms'; @import 'typography/gradients'; @import 'typography/hover-img'; @import 'typography/icons'; ... и т.д. // стили шаблона @import 'template/comments'; @import 'template/components'; @import 'template/forms'; @import 'template/lightslider'; @import 'template/menu-simple'; @import 'template/message'; @import 'template/page-other-pages'; @import 'template/plugins'; ... и т.д.
Lazy Colors
Файл lazy/3-colors.scss содержит цвета Berry CSS. Это значит, что пользователь MaxSite CMS может использовать в текстах эти утилитарные классы. Так же эти классы используются при разработке шаблона, например в компонентах шапки/подвала или into-top-файлах. Сейчас в Berry CSS очень много цветов, причём с мелкой градацией (шаг 50 в material design от google). За это приходится платить увеличенным размером css-файла (58Кб). Этот фактор, а также то, что цвет всё-таки вторичен при отрисовки страницы, он загружается как lazy.
При необходимости можно отредактировать файлberry/variables/_colors.scss
и указать только избранные цвета. Или использовать_colors-lite.scss
, где используется более крупная градация «100». Это уменьшит размер файла примерно в 2 раза.
В любом случае утилитарные классы цветов компилируются как угодно. Стоит отметить еще один момент. Файл _all-colors.scss
содержит sass-переменные всех цветов. И эти переменные могут использоваться в других файлах вместо явного указания цвета.
Остальные файлы
Они подключаются уже произвольно, хотя чуть больший приоритет отдан иконкам FontAwesome.
Итоги
Данный подход позволяет решить сразу несколько проблем с организацией css-кода. Важно то, что он использует современные возможности браузеров и учитывает требования поисковиков по быстрому отображению сайта, особенно на мобильных устройствах. Особый выигрыш получается от использования утилитарных классов: больше нет необходимости ждать их позднюю загрузку и блоки на странице сразу отображаются корректно.