Перейти к содержанию

Слои каскада

В первых шестнадцати уроках каталог получил структуру, сетку, форму и оформление. Вместе с ними файл стилей постепенно вырос: одна карточка встречается в нескольких правилах, а окончательный результат приходится объяснять и специфичностью, и местом записи. Теперь приведём этот опыт к явной структуре. Слои каскада позволяют заранее определить порядок крупных групп, сохраняя обычные селекторы внутри каждой группы.

Продолжим layout-lab из снимка lesson-16. Шесть названий, количества уроков и реальные ссылки останутся прежними. Результат этой страницы — разделение сброса, базовых значений, компонентов, вариантов и правил вывода. Это механизм организации CSS; он не добавляет новых курсов и не меняет HTML в зависимости от браузера.

Порядок групп до сравнения селекторов

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

Полностью замените styles.css файлом ниже. Это полный перенос принятого состояния шестнадцатого урока, а не добавка к прежнему CSS. Старые правила вне слоёв должны быть удалены: иначе они будут участвовать в другом порядке и помешают наблюдать именно слои. index.html, receipt.html и обложку сейчас не изменяем.

@layer reset, base, components, variants, utilities;
@layer reset {
  .layout-lab, .layout-lab *, .layout-lab *::before, .layout-lab *::after { box-sizing: border-box; }
  .layout-lab { margin: 0; }
  .layout-lab h1, .layout-lab h2, .layout-lab h3, .layout-lab p { margin-block: 0; }
  .layout-lab .course-list { list-style: none; padding: 0; margin-block: 1.5rem 0; }
}
@layer base {
  .layout-lab { --lab-bg: #1f202a; --lab-surface: #252631; --lab-ink: #e4e4e4; --lab-muted: #b7b9c9; --lab-link: #46d96f; --lab-accent: #6c43af; --lab-line: #3b3c4c; --lab-gap: 1rem; min-block-size: 100vh; display: flex; flex-direction: column; background: var(--lab-bg); color: var(--lab-ink); font-family: system-ui, sans-serif; line-height: 1.6; }
  .layout-lab a { color: var(--lab-link); }
  .layout-lab a:hover { text-decoration-thickness: .15em; }
  .layout-lab :is(a, button, input, select, summary, [tabindex]):focus-visible { outline: 3px solid #83eba0; outline-offset: 3px; }
}
@layer components {
  .layout-lab .lab-shell { inline-size: min(100% - 2rem, 72rem); margin-inline: auto; }
  .layout-lab .lab-header, .layout-lab .lab-footer { padding-block: 1.25rem; }
  .layout-lab .lab-main { flex: 1; padding-block: 2rem; }
  .layout-lab .lab-header-row, .layout-lab .lab-nav, .layout-lab .lab-footer-row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
  .layout-lab .lab-header-row, .layout-lab .lab-footer-row { justify-content: space-between; }
  .layout-lab .lab-brand { font-weight: 700; }
  .layout-lab .lab-intro { max-inline-size: 65ch; margin-block-end: 1.5rem; }
  .layout-lab .lab-title { font-size: clamp(1.75rem, 1.2rem + 2vw, 3rem); line-height: 1.15; }
  .layout-lab .lab-intro > * + *, .layout-lab .lab-request > * + * { margin-block-start: 1rem; }
  .layout-lab .course-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr)); gap: var(--lab-gap); }
  .layout-lab .course-slot { container-type: inline-size; container-name: course; min-inline-size: 0; }
  .layout-lab .course-card { background: var(--lab-surface); border: 1px solid var(--lab-line); border-radius: .5rem; padding: 1.25rem; block-size: 100%; }
  .layout-lab .course-cover { display: block; max-inline-size: 100%; block-size: auto; }
  .layout-lab .course-title { font-size: 1.25rem; line-height: 1.3; }
  .layout-lab .course-card > * + * { margin-block-start: .75rem; }
  .layout-lab .course-description { overflow-wrap: anywhere; }
  .layout-lab .course-meta { display: flex; flex-wrap: wrap; gap: .5rem 1rem; align-items: baseline; }
  .layout-lab .course-count { color: var(--lab-muted); }
  .layout-lab .lab-request { margin-block-start: 3rem; }
  .layout-lab .lab-request label { display: block; }
  .layout-lab .lab-request form > * + * { margin-block-start: 1rem; }
  .layout-lab .lab-request :is(input, select, button) { font: inherit; border: 1px solid var(--lab-line); border-radius: .35rem; padding: .6rem .75rem; max-inline-size: 100%; }
  .layout-lab .lab-request :is(input, select) { background: var(--lab-surface); color: var(--lab-ink); }
  .layout-lab .lab-request button { background: var(--lab-accent); color: #fff; cursor: pointer; }
  .layout-lab .lab-request button:active { transform: translateY(1px); }
  .layout-lab .lab-details { margin-block-start: 1.5rem; }
  .layout-lab .lab-details summary { cursor: pointer; }
  .layout-lab .lab-details[open] summary { margin-block-end: .5rem; }
  .layout-lab .lab-footer { border-block-start: 1px solid var(--lab-line); }
  @container course (min-width: 32rem) {
    .layout-lab .course-card { display: grid; grid-template-columns: 10rem minmax(0, 1fr); gap: .75rem 1.25rem; align-content: start; }
    .layout-lab .course-cover { grid-row: 1 / 4; inline-size: 100%; }
    .layout-lab .course-card > * + * { margin-block-start: 0; }
  }
  @media (max-width: 42rem) {
    .layout-lab .lab-header-row { align-items: start; flex-direction: column; }
    .layout-lab .lab-main { padding-block: 1.5rem; }
  }
}
@layer variants {
  .layout-lab .course-card--featured { border-color: var(--lab-accent); }
}
@layer utilities {
  @media print {
    .layout-lab { background: #fff; color: #111; display: block; }
    .layout-lab .course-card { background: #fff; border-color: #aaa; break-inside: avoid; }
    .layout-lab a, .layout-lab .course-count { color: #111; }
    .layout-lab .lab-request, .layout-lab .lab-nav { display: none; }
  }
}

Первая строка задаёт порядок сразу, до содержимого блоков. reset занимается коробочной моделью и начальными отступами. base задаёт палитру, шрифт, ссылки и видимый фокус. components описывает устройство страницы и карточек. variants выделяет первый курс цветом границы. utilities содержит печать. Названия выбирает автор проекта; браузер не приписывает слову utilities особых встроенных полномочий.

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

Само разделение на файлы не задаёт порядок групп. Два отдельных stylesheet всё ещё могут содержать правила одного слоя, а один stylesheet — несколько разных слоёв. Поэтому архитектуру лучше читать по объявлениям ответственности, а не угадывать по имени файла.

Повторное открытие существующего слоя

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

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

@layer components {
  .layout-lab .course-card.course-card--featured {
    border-color: #46d96f;
  }
}

У выделенной карточки ожидается прежняя фиолетовая граница. Новая зелёная граница находится в components, тогда как обычное объявление фиолетового цвета — в более позднем variants. Дополнительный класс увеличил специфичность, но не переместил декларацию в другой слой. Это удобный случай, где чтение только последнего правила приводит к неверному прогнозу.

Теперь временно перенесите этот же диагностический блок из components в variants, сохранив его в конце. Ожидается зелёная граница: оба объявления уже принадлежат одной группе, и более специфичный селектор получает значение. Верните основной файл после опыта. Менялась не сила цвета и не сам элемент, а место объявления в алгоритме выбора.

Правила вне слоёв и важные объявления

Обычное авторское объявление вне слоёв имеет приоритет над обычными объявлениями внутри слоёв. Поэтому оставленный после переноса старый .course-card способен неожиданно перекрыть специально созданный variants. Добавление нового слоя не поглощает существующий CSS автоматически. Перед переносом внешней библиотеки или прежнего файла нужно решить, какую часть каскада она будет занимать.

У важных объявлений порядок слоёв разворачивается: ранние слои имеют преимущество перед поздними, а важные объявления в слоях — перед важными несгруппированными. Это существенное ограничение, а не исключение, которое можно игнорировать. В основном примере важные значения не нужны, поэтому обычный порядок остаётся ясно видимым. Позднее отдельное правило скрытого HTML-состояния будет отмечено и объяснено по своей обязанности.

Такой разбор не заменяет остальные ступени каскада. Источник стиля, важность, анимации и переходы также влияют на итог. Здесь мы не утверждаем, что последний слой вообще сильнее всего возможного CSS. Рассматриваем конкретное соперничество обычных авторских правил нашего файла. Порядок и особенность важных значений описаны в MDN о слоях каскада.

Группы описывают ответственность

Слой не создаёт область изоляции селекторов. Правило p внутри components всё равно может найти все подходящие абзацы документа. Поэтому мы сохранили префикс .layout-lab и имена компонентов. Слои организуют приоритет, а селекторы определяют элементы; это разные обязанности. Перенести глобальное правило в группу недостаточно, чтобы превратить его в локальную настройку карточки.

Пользовательские свойства также остаются частью каскада и наследования. Палитра объявлена на .layout-lab, затем дочерние элементы читают её через var. Значения не закрепляются за слоем навсегда: другой подходящий селектор может объявить новое значение. Выбор слоя делает такое изменение обозримым, но не запрещает его. При настройке темы полезно сначала определить место значения, затем место его использования.

Не создавайте отдельный слой для каждой карточки. В нашем каталоге шесть записей, но устройство у них общее. Отдельный вариант первой карточки относится к одной ответственности, а её название и ссылка остаются данными HTML. Слишком дробная иерархия снова заставила бы помнить случайный порядок вместо нескольких устойчивых групп.

Браузер без поддержки @layer проигнорирует неизвестные блоки, а не выполнит их как обычные правила. Для него этот файл не является полноценным запасным оформлением. Современную поддержку проверяйте по таблице первичного справочника для ваших целевых браузеров; в завершающем уроке сохраним отдельный плоский вариант. Сам HTML уже содержит все ссылки и подписи, поэтому содержание не возникает из CSS.

Теперь перенос стиля имеет проверяемую границу: тот же каталог, те же данные и пять именованных групп. В следующем уроке уточним направление размеров и отступов внутри этих групп, не меняя их установленного порядка.

Полное состояние этого шага находится в layout-lab/lesson-17 внутри исходников продолжения. Каждый снимок открывается отдельно; подключать CSS разных уроков одновременно не нужно.

Оглавление курса.