Веб-программирование
Веб-программирование · лекция 5 из 16
clamp(), @containertransform, transition, @keyframesВсё это сошлось в одной карточке. Сегодня карточек триста, и пишут их пять человек одновременно.
~200
строк, один автор
100 000+
строк, десятки авторов
Разница не только в объёме. На двухстах строках все правила помещаются в голове. На ста тысячах — ни в чьей.
Наш проект на сегодня. Лендинг небольшой кофейни: шапка, меню, кнопка
«Заказать». Один разработчик, один style.css.
За лекцию он вырастет в сеть из тридцати кофеен. Счётчик в углу покажет, как.
Каждый инструмент появится ровно тогда, когда без него станет больно.
Когда CSS становится много
Ни одна из них не видна на двухстах строках. Все три — неизбежны на двух тысячах.
.title {
color: #7a5530;
font-size: 26px;
}
.title {
color: #fff;
font-size: 14px;
}
В CSS нет областей видимости. Оба файла подключены на сайте, и
.title в меню и .title в доставке — это
один и тот же .title.
Специфичность одинаковая — 0,1,0. Значит, по правилам
каскада из лекции 2 решает порядок: побеждает файл, подключённый
позже.
<link rel="stylesheet" href="menu.css">
<link rel="stylesheet" href="delivery.css"> <!-- победит -->
Борис ничего не ломал. Он даже не открывал страницу меню. Но заголовок меню теперь белый, четырнадцатым кеглем.
🥚 На главной странице курса есть пасхалка !important
!important против !important: снова решает
специфичность и порядок, только теперь перебить нечем. Следующий шаг —
style="" прямо в разметке.
.promo-banner { … } /* кто-то использует? */
.promo-banner-new { … } /* вроде новый */
.promo-banner-new-2 { … }
.old-header .title { … } /* старая шапка? её уже нет? */
.title-fix { … }
.title-fix-final { … }
.title-fix-final-v2 { … } /* не трогать!!! */
.btn, .button, .btn-main { … }
.mt-10-important { margin-top: 10px !important; }
append-only
По CSS нельзя понять, кто им пользуется. Класс могут добавлять из
шаблона, из JavaScript, из письма рассылки. Удалишь
.promo-banner — и где-то сломается то, чего ты не видел.
Поэтому безопасная стратегия — никогда ничего не удалять и дописывать поверх. Через год половина файла — мёртвый код, который все боятся тронуть.
main
Аня сверстала меню с коричневой кнопкой, Борис — доставку с зелёной.
У обоих есть классы .title, .item и
.button. По отдельности всё выглядит как задумано.
Ветки сливают в общую. Какого цвета станет кнопка в меню?
Методологии и БЭМ
styles/
base/ сброс, типографика, переменные
reset.css
tokens.css
components/ один компонент — один файл
button.css
card.css
header.css
pages/ то, что есть только на одной странице
delivery.css
main.css только подключения, без правил
Удаляете компонент — удаляете его файл. Ничего не остаётся висеть, и не нужно гадать, какие правила были «его».
Две тысячи строк никуда не делись, но теперь у каждой строки есть
адрес: стиль кнопки ищут в button.css,
а не поиском по всему проекту.
/* main.css — от общего к частному */
@import "base/reset.css";
@import "components/button.css";
@import "components/card.css";
@import "pages/delivery.css";
При равной специфичности побеждает то, что подключено позже. Поэтому частное — в конце: странице можно поправить компонент, а компоненту сброс.
Склеивать файлы в один будет сборщик — это лекция 10.
Папки не спасают от главной болезни: .title в
menu.css и .title в delivery.css
всё ещё один и тот же класс.
Нужно правило, по которому два человека, не договариваясь, придумают разные имена. Такие правила называются методологиями.
Придумали в Яндексе. Десятки сервисов, сотни разработчиков, общие шапки, поиск и кнопки — проблема общих имён там стояла в полный рост.
Сегодня это самая распространённая методология именования в мире, а не только в России.
bem.info — методология БЭМcard, menu, buttoncard__titlebutton--primaryВся методология — это три понятия и два разделителя.
card__title--large
__ два подчёркивания — дальше элемент title-- два дефиса — дальше модификатор largeПо одному имени понятно, где искать стиль и кому он принадлежит.
<article class="coffee coffee--featured">
<div class="coffee__pic">☕</div>
<h3 class="coffee__name">
Капучино
</h3>
<button class="coffee__order">
Заказать
</button>
</article>
.coffee { … }
.coffee--featured { … }
.coffee__pic { … }
.coffee__name { … }
.coffee__order { … }
В CSS — плоский список одиночных классов. Ни тегов, ни вложенности.
В исходном БЭМ Яндекса модификатор отделяется одним подчёркиванием:
button_primary. Вариант с двумя дефисами —
button--primary — придумали позже, он читается легче и
сейчас встречается чаще.
Какой выбрать — неважно. Важно, чтобы весь проект писал одинаково. Мы используем два дефиса.
.menu li a { … }
#header .title { … }
.menu__link { … }
.header__title { … }
У всех правил одинаковая специфичность — 0,1,0. Войне из
части 1 не на чем стоять: перебивать нечего, побеждает
порядок, и он предсказуем.
.menu__list__item__link — повторяет вложенность DOM.menu__link — элемент принадлежит блокуРазметку внутри блока будут переделывать. Если имя повторяет структуру, при каждой переделке придётся переименовывать и CSS.
<button class="button--primary">
<button class="button button--primary">
.button даёт всё общее — отступы, шрифт, скругление.
.button--primary добавляет только отличие — цвет. Без
основного класса у кнопки нет основы.
.card {
padding: 16px;
margin-top: 24px;
}
.card { padding: 16px; }
.catalog__card {
margin-top: 24px;
}
Внутренние отступы — дело блока, внешние и позиция — дело того, кто его использует. Иначе блок не переиспользовать: в каждом новом месте его отступ будет мешать.
<ul class="menu">
<li class="menu__item card">…</li>
</ul>
Один элемент — одновременно menu__item и блок
card. Меню отвечает за положение карточки в списке,
карточка — за свой вид.
Так правило 4 и выполняется на практике: внешнее задаёт
.menu__item, внутреннее — .card.
<button class="order-form__submit
order-form__submit--disabled">
Длинно. Некрасиво. Классы повторяют имя блока по десять раз.
Зато через полгода по этой строке понятно всё: какая форма, какая кнопка, в каком состоянии и в каком файле её стиль. В команде из двадцати человек это дороже красоты.
Аня называет классы menu__…, Борис —
delivery__…. Свойства те же, цвета те же. Сливаем ещё раз.
Десять фрагментов кода. В каждом нужно решить: всё по правилам или что-то нарушено — и что именно.
Идеи у всех общие: называть по назначению, а не по виду
(.warning, а не .red), и держать
специфичность низкой.
Запоминать не нужно — нужно узнавать в чужом коде.
.titleИмена пишутся короткими, как в первой ЛР. Конфликт невозможен технически: у каждого файла свой хвост.
Следующий шаг той же идеи: стили пишутся прямо в коде компонента на JavaScript, а уникальные имена генерируются на лету, при работе страницы.
Несколько лет это был самый модный подход в мире React. Сейчас от него во многом уходят: генерация стилей в браузере стоит времени при каждой отрисовке.
Знать слово нужно: в чужих проектах он встретится.
Sass и вложенность
Один и тот же набор правил теперь написан десять раз с разными цветами. И каждое исправление — тоже десять раз.
.shop--arbat .button { background: #7a5530; }
.shop--arbat .title { color: #7a5530; }
.shop--arbat .card { border-color: #7a5530; }
.shop--lesnaya .button { background: #2e7d32; }
.shop--lesnaya .title { color: #2e7d32; }
.shop--lesnaya .card { border-color: #2e7d32; }
/* …и ещё восемь раз: в CSS нет циклов */
Браузер никогда не узнает, что вы писали на Sass.
Самый старый и самый распространённый препроцессор. Два синтаксиса: старый — на отступах, без скобок, и SCSS — надмножество CSS: любой CSS-файл уже является корректным SCSS.
Сегодня почти все пишут на SCSS. Расширение файла — .scss.
$brown: #7a5530;
$radius: 10px;
.button {
background: $brown;
border-radius: $radius;
}
Знак доллара вместо двух дефисов. Выглядит знакомо — но работает совсем не так, как переменные из лекции 4.
$brown против --brownSass: $brown | CSS: --brown | |
|---|---|---|
| когда работает | при сборке | в браузере |
| браузер видит | #7a5530 | var(--brown) |
| наследуется | нет | да |
| меняется на лету | нет | да |
Тёмную тему из лекции 4 на $-переменных
не сделать: к моменту, когда браузер узнал о
prefers-color-scheme, переменных уже нет.
--переменныецвета, отступы, темы — всё, что может поменяться при работе страницы
$переменныевычисления и списки, нужные только при сборке: брейкпоинты, карты цветов для циклов
Часто их сочетают: --brown: #{$brown}; — значение из
Sass кладут в настоящую CSS-переменную.
.card {
padding: 16px;
&__title { color: $brown; }
&--featured { border: gold; }
&:hover { … }
}
.card { padding: 16px; }
.card__title { color: #7a5530; }
.card--featured { border: gold; }
.card:hover { … }
& — «родительский селектор». &__title
склеивается в .card__title: весь блок БЭМ в одном месте.
@mixin button($bg) {
padding: 10px 18px;
border-radius: $radius;
background: $bg;
color: #fff;
}
.button { @include button($brown); }
.button--green { @include button(#2e7d32); }
Кусок правил с параметрами. При сборке он копируется в каждое место, где подключён.
@use// main.scss
@use "base/tokens";
@use "components/button";
@use "components/card";
Та же раскладка по папкам из части 2. Файлы с подчёркиванием —
_button.scss — сами по себе не компилируются, только
подключаются.
На выходе один style.css, без лишних
запросов, как у @import в браузере.
$shops: (arbat: #7a5530, lesnaya: #2e7d32, neva: #2f6aa3);
@each $name, $color in $shops {
.shop--#{$name} {
.button { background: $color; }
.title { color: $color; }
}
}
Цикл по карте цветов. Новая кофейня — одна строка в
$shops, а не тридцать правил.
.page { .content { .card { .title { a { … } } } } }.card { &__link { … } }
Первое скомпилируется в .page .content .card .title a —
та же война специфичностей из части 1, только её не видно в
исходнике. Правило: не глубже одного-двух уровней.
.card {
padding: 16px;
&:hover { background: #fbf6ef; }
.card__title { color: var(--brown); }
}
Без сборки, прямо в браузере, во всех современных браузерах.
.card {
&__title { color: brown; }
}
В Sass это .card__title. А если этот код написать в
обычном .css-файле?
&__title не работает.
& там — целый селектор, а не кусок текста.
Браузер прочитает __title как имя тега, и правило не
применится ни к чему.
Поэтому для БЭМ в чистом CSS пишут полное имя элемента:
.card__title внутри .card { } или просто
отдельным правилом.
| возможность | теперь |
|---|---|
| переменные | custom properties, нативно |
| вложенность | нативно |
| вычисления | calc(), clamp(), нативно |
| цветовые функции | color-mix(), нативно |
склейка имён &__title | только Sass |
| миксины, циклы, модули | только Sass |
Sass уже не обязателен, но читать его нужно уметь.
Другие препроцессоры с теми же идеями: переменные, вложенность, миксины. Less — на нём был написан Bootstrap до 4-й версии. Stylus — самый лаконичный синтаксис.
Новые проекты на них почти не начинают. Встречаются в легаси — и после Sass читаются без подготовки.
Конвейер для CSS
Новое свойство старый Safari понимает только под своим именем — с
префиксом -webkit-.
.card__pic {
-webkit-backdrop-filter: blur(8px); /* старый Safari */
backdrop-filter: blur(8px); /* все остальные */
}
Какие свойства в каких браузерах нужно дублировать — меняется каждый месяц. Помнить это вручную невозможно, а забытый префикс находит только пользователь.
.card__pic {
-webkit-backdrop-filter: blur(8px); /* дописал он */
backdrop-filter: blur(8px); /* писали мы */
}
Префиксы дописываются по данным Can I Use и по списку браузеров, которые проект обещает поддерживать:
// .browserslistrc
> 0.5%, last 2 versions, not dead
Позволяет писать CSS, который понимают ещё не все браузеры. То, что поддержано везде, остаётся как есть; остальное переписывается в понятные всем конструкции.
По сути — Babel для CSS. С ним можно пользоваться новыми возможностями из лекции 4, не дожидаясь, пока обновятся браузеры пользователей.
.card__title {
margin: 0px 0px 0px 0px;
color: #ffffff;
/* заголовок карточки */
}
.card__title{margin:0;color:#fff}
Пробелы, комментарии, лишние нули и повторы — всё, что нужно человеку и не нужно браузеру. Файл становится на треть меньше.
Скорее всего, вы будете пользоваться им, ни разу не установив вручную:
другой язык, который компилируется в CSS
обработка уже готового CSS: CSS на входе, CSS на выходе
Они не соперники. В одном проекте часто работают оба: сначала Sass собирает CSS, потом PostCSS расставляет префиксы и сжимает.
Чужой CSS вместо своего
Писать кнопки, формы и таблицы с нуля некогда. Нужны готовые.
Сделали в Twitter для своих внутренних инструментов. Готовые сетка, кнопки, формы, таблицы, меню, модальные окна — всё на классах.
Подключаете один CSS-файл, пишете разметку с нужными классами — и получаете аккуратный интерфейс, не написав ни строчки CSS.
getbootstrap.com<form class="card p-3">
<label class="form-label">Напиток</label>
<select class="form-select mb-3">…</select>
<button class="btn btn-primary">Заказать</button>
</form>
mb-3 — margin-bottom третьего размера, p-3 —
padding. Запомните эти классы, они ещё понадобятся.
<div class="row">
<div class="col-md-8">Заказы</div>
<div class="col-md-4">Остатки</div>
</div>
Двенадцать колонок: на широком экране 8 + 4, на узком — друг под другом. Внутри — тот же Flexbox из лекции 3 и те же медиазапросы из лекции 4. Никакой магии.
Десять разных сайтов. Отличаются только надписью в шапке.
232 КБ
столько весит bootstrap.min.css. Сетка, карусель,
модальные окна, подсказки, аккордеоны — даже если вам нужна была
только кнопка.
Обычно используется несколько процентов. Остальное браузер всё равно скачивает и разбирает.
/* хотим свою кнопку — перебиваем Bootstrap */
.btn-primary {
background: #7a5530 !important;
border-color: #7a5530 !important;
}
.navbar .nav-link.active { … } /* длиннее, чем у них */
Свой дизайн поверх фреймворка — это та же война специфичностей из части 1. Только противник — двести килобайт чужого CSS.
@layer@layer reset, framework, components;
@import "bootstrap.css" layer(framework);
@layer components {
.btn-primary { background: #7a5530; }
}
Каскадные слои. Порядок объявлен в первой строке, и слой, объявленный
позже, побеждает — независимо от специфичности. Без
!important.
По специфичности сильнее framework: у него id. Но слои
сравниваются раньше специфичности.
components объявлен позже
framework — и выигрывает, хотя внутри у
framework селектор с id.
Фреймворк кладут в нижний слой — и больше не воюют с ним. Каскад из лекции 2 получил ещё одну ступень: происхождение → слой → специфичность → порядок.
Админка для бариста — идеальный случай. Сайт кофейни — нет.
Bulma, Foundation — та же идея, другие классы. Выбирают по вкусу и по тому, что уже знает команда.
В мире React идею продолжают библиотеки компонентов — MUI, shadcn/ui: там готовы не классы, а целые компоненты с поведением. Это лекция 13.
Tailwind
БЭМ работает, но CSS всё равно растёт: каждый новый компонент приносит новые правила с теми же отступами и цветами.
.coffee__order { padding: 8px 16px; border-radius: 8px; }
.cart__checkout { padding: 8px 16px; border-radius: 8px; }
.promo__more { padding: 8px 16px; border-radius: 6px; }
.vacancy__apply { padding: 8px 16px; border-radius: 8px; }
Четыре блока, четыре имени — и почти одинаковые свойства. Тысячный компонент добавит ещё одну такую строку.
Не придумывать имя каждому компоненту, а собирать вид из маленьких одноцелевых классов прямо в разметке:
Один класс — одно свойство. p-4 — это
padding: 1rem, rounded-xl — скругление,
shadow — тень.
<div class="p-4 rounded-2xl border-2 border-coffee-100">
<h3 class="text-xl font-bold text-coffee-900">Капучино</h3>
<p class="text-sm text-neutral-500">Эспрессо и молоко</p>
<button class="px-3 py-1.5 rounded-lg bg-coffee-700">
Заказать
</button>
</div>
Своего CSS — ноль строк. Всё, что знает карточка о своём виде, написано прямо на ней.
style=""style="padding: 13px"
class="p-3" /* 12px */
class="p-4" /* 16px */
p-4 — не любое число, а шаг из шкалы дизайн-системы.
Inline-стиль разрешает 13px, утилиты — нет. Поэтому
тридцать кофеен выглядят как одна система, а не как
тридцать разных сайтов.
hover:, focus: — псевдоклассы из лекции 2.
md:, lg: — медиазапросы из лекции 4. В
атрибуте style ни того, ни другого не напишешь.
Новый компонент собирается из классов, которые уже есть. Тысячный экран добавляет в CSS ноль строк — меняется только разметка.
Утилит в Tailwind — десятки тысяч. В сборку попадают только те, что реально встретились в ваших файлах: инструмент просматривает разметку и генерирует CSS под неё.
Сравните с Bootstrap: там в браузер уезжает всё, что есть во фреймворке, а здесь — только то, что есть в проекте.
Самый популярный utility-first инструмент. Работает поверх PostCSS — та самая связка из части 4.
tailwindcss.com@import "tailwindcss";
@theme {
--color-coffee-50: #f6efe6;
--color-coffee-700: #7a5530;
--font-sans: "PT Sans", sans-serif;
}
Объявили --color-coffee-700 — и в проекте появились
классы bg-coffee-700, text-coffee-700,
border-coffee-700. Дизайн-токены из лекции 4 в чистом
виде.
<button class="inline-flex items-center gap-2 px-4 py-2
rounded-lg bg-coffee-700 text-white text-sm font-bold
shadow-sm hover:bg-coffee-900 focus:ring-2
focus:ring-coffee-300 disabled:opacity-50 md:px-6">
<CoffeeCard /> — классы написаны один раз внутри компонентаПоэтому Tailwind так популярен именно в React: там разметку всё равно режут на компоненты, и длинная строка классов живёт в одном месте.
@apply.btn {
@apply px-4 py-2 rounded-lg bg-coffee-700 text-white;
}
Способ собрать утилиты обратно в свой класс. Выглядит как спасение от длинных строк — но если делать так везде, получится обычный CSS с лишним шагом сборки.
Авторы Tailwind советуют им не злоупотреблять: повторы — компонентами,
@apply — для редких исключений.
Как выбирать
Чистый CSS, БЭМ, Bootstrap и Tailwind. Внешне карточки одинаковые. Разница в том, где живут решения и сколько CSS уезжает в браузер.
| вопрос | если да |
|---|---|
| CSS пишут больше двух человек? | методология: БЭМ или CSS Modules |
| Своего дизайна нет, а срок вчера? | фреймворк: Bootstrap |
| Проект на компонентах (React, Vue)? | CSS Modules или Tailwind |
| Проект проживёт годы? | что-то из этого — обязательно |
Страница из ЛР на двести строк не нуждается ни в чём из этого. И это нормально.
В React-проекте во второй половине курса стили будут на CSS Modules или Tailwind. Сегодня стало понятно, зачем каждый из них и какую проблему он решает.
А пока — для своей страницы из ЛР 2 уже сейчас можно взять две вещи: раскладку по файлам и имена по БЭМ.
Пять лекций страница выглядела. Дальше она начнёт думать. На этой же лекции — ЛР 3.
Слайды, демо и игра — в репозитории курса