🔍 Начните печатать, чтобы искать по книге или перейти к нужной странице по номеру

Удобно листать не только прокруткой, но и клавишами‑стрелками:

 
между важными местами
Shift
между
разворотами
Васи­лий Полов­нёв, Игорь Пет­ров

ХТМЛ.
Вёрстка сайтов

Изда­тель­ство Бюро Гор­бу­нова
2021
Василий Половнёв, Игорь Петров

ХТМЛ.
Вёрстка сайтов

Издательство Бюро Горбунова
2021
удк 004.42
ббк З973.42
П52
Васи­лий Полов­нёв, Игорь Пет­ров
П52
ХТМЛ. Вёрстка сай­тов для дизай­не­ров, редак­то­ров и руко­во­ди­те­лей.—
М.: Изд‑во Бюро Гор­бу­нова, 2021

Эта книга — поша­го­вая инструк­ция по вёрстке сай­тов на язы­ках ХТМЛ и ЦСС. Вы узна­ете не только как свер­стать сайт, но и как опуб­ли­ко­вать его в интер­нете, настро­ить кра­си­вый шаринг в соц­сети и под­клю­чить системы аналитики.

Отдель­ный раз­дел книги посвя­щён работе с вер­сталь­щи­ком: как ста­вить задачи, полу­чать пред­ска­зу­е­мый резуль­тат и пра­вильно при­ни­мать и внед­рять вёрстку.

Оглавление

удк 004.42
ббк З973.42
П52
П52
Василий Половнёв, Игорь Петров
ХТМЛ. Вёрстка сайтов для дизайнеров, редакторов и руководителей.—
М.: Изд‑во Бюро Горбунова, 2021

Эта книга — пошаговая инструкция по вёрстке сайтов на языках ХТМЛ и ЦСС. Вы узнаете не только как сверстать сайт, но и как опубликовать его в интернете, настроить красивый шаринг в соцсети и подключить системы аналитики.

Отдельный раздел книги посвящён работе с верстальщиком: как ставить задачи, получать предсказуемый результат и правильно принимать и внедрять вёрстку.

Оглавление

Знакомство с ХТМЛ

ХТМЛ‑файл в текстовом редакторе

Что такое веб‑страница

Любая веб‑стра­ница — это про­сто тек­сто­вый файл с осо­бой разметкой.

Чтобы создать стра­ницу, доста­точно создать у себя на ком­пью­тере файл с любым име­нем и рас­ши­ре­нием .html, затем открыть его в тек­сто­вом редак­торе и напи­сать внутри любой текст.

ХТМЛ‑файл в браузере

Если затем пере­та­щить этот файл в бра­у­зер, мы уви­дим стра­ницу с тек­стом, сей­час она выгля­дит про­сто и неприглядно.

Чтобы стра­ницу было удобно читать и изу­чать, её содер­жи­мое осо­бым обра­зом струк­ту­ри­руют и оформ­ляют. Сей­час раз­бе­рёмся, как это делают.

Редактировать ХТМЛ‑файлы можно в стандартной программе «Блокнот», но удобнее использовать более продвинутые текстовые редакторы. Например, несложный в освоении бесплатный редактор «Саблайм»

Знакомство с ХТМЛ

ХТМЛ‑файл в текстовом редакторе

Что такое веб‑страница

Любая веб‑страница — это просто текстовый файл с особой разметкой.

Чтобы создать страницу, достаточно создать у себя на компьютере файл с любым именем и расширением .html, затем открыть его в текстовом редакторе и написать внутри любой текст.

ХТМЛ‑файл в браузере

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

Чтобы страницу было удобно читать и изучать, её содержимое особым образом структурируют и оформляют. Сейчас разберёмся, как это делают.

Редактировать ХТМЛ‑файлы можно в стандартной программе «Блокнот», но удобнее использовать более продвинутые текстовые редакторы. Например, несложный в освоении бесплатный редактор «Саблайм»

Лайфхак: песочницы для кода

Если не хочется писать код в фай­лах, можно делать это на одном из мно­же­ства сай­тов‑песоч­ниц. На таких сай­тах есть редак­тор и окошко с резуль­та­том вёрстки, кото­рый обнов­ля­ется в реаль­ном вре­мени при изме­не­нии кода.

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

Самые популярные и простые в освоении песочницы: Кодпен и Код Сэндбокс

Лайфхак: песочницы для кода

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

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

Самые популярные и простые в освоении песочницы: Кодпен и Код Сэндбокс

Теги

Чтобы струк­ту­ри­ро­вать и офор­мить стра­ницу, её раз­ме­чают язы­ком ХТМЛ.

ХТМЛ‑раз­метка состоит из кон­тей­не­ров с содер­жи­мым — тегов. Текст поме­щают внутрь тега, между его откры­ва­ю­щей и закры­ва­ю­щей частями:

<h1>Заголовок</h1>
<p>Текст абзаца.</p>
<p>Текст второго абзаца.</p>

Про­сто отбить один абзац от дру­гого энте­рами, как в Ворде, не полу­чится — бра­у­зеры вос­при­мут эти энтеры как пробелы.

Назва­ние тега отра­жает его смысл. Напри­мер, h1 — heading 1 — заго­ло­вок пер­вого уровня.

Теги можно вкла­ды­вать друг в друга. Напри­мер, сде­лать жир­ный текст внутри абзаца или пункты внутри списка:

<p>По-настоящему <b>важные принципы</b>:</p>

<ul>
  <li>Не планировать впритык</li>
  <li>Сделать значит сдать</li>
  <li>Флекс — всегда боль</li>
</ul>

Есть теги, кото­рые не нужно закры­вать и в кото­рые нельзя ничего вло­жить. Напри­мер, тег пере­носа строки br:

<h1>
  Лев Николаевич Толстой. <br>
  Жизнь и взгляды
</h1>

Не закры­ва­ются теги кар­ти­нок и раз­ных интер­ак­тив­ных эле­мен­тов: видео, полей ввода, кно­пок. Все эти эле­менты мы раз­бе­рём дальше в книге.

Ещё в ХТМЛ есть ком­мен­та­рии. Это спе­ци­аль­ный текст, кото­рый не будет видно на стра­нице, только в коде. Раз­ра­бот­чики исполь­зуют ком­мен­та­рии в каче­стве под­ска­зок себе и дру­гим людям, рабо­та­ю­щим с кодом.

Ком­мен­та­рии пишут внутри кон­струк­ции <!-- … -->, это поз­во­ляет удобно пря­тать любые теги — доста­точно допи­сать вос­кли­ца­тель­ный знак и дефисы в начале и конце тега и он пре­вра­тится в ком­мен­та­рий, про­па­дёт со стра­ницы, при этом остав­шись в коде. На языке раз­ра­бот­чи­ков это назы­ва­ется «заком­мен­тить» — спря­тать кусок вёрстки.

<p>Автор: Галина Игнатова</p>

<!-- Менять код только с разрешения главреда! -->
<p>Издание «Валенсия», код агента 348187</p>

<!-- Источники, закомментить, когда ссылок нет  -->
<!-- ul>
  <li></li>
  <li></li>
</ul -->

Теги

Чтобы структурировать и оформить страницу, её размечают языком ХТМЛ.

ХТМЛ‑разметка состоит из контейнеров с содержимым — тегов. Текст помещают внутрь тега, между его открывающей и закрывающей частями:

<h1>Заголовок</h1>
<p>Текст абзаца.</p>
<p>Текст второго абзаца.</p>

Просто отбить один абзац от другого энтерами, как в Ворде, не получится — браузеры воспримут эти энтеры как пробелы.

Название тега отражает его смысл. Например, h1 — heading 1 — заголовок первого уровня.

Теги можно вкладывать друг в друга. Например, сделать жирный текст внутри абзаца или пункты внутри списка:

<p>По-настоящему <b>важные принципы</b>:</p>

<ul>
  <li>Не планировать впритык</li>
  <li>Сделать значит сдать</li>
  <li>Флекс — всегда боль</li>
</ul>

Есть теги, которые не нужно закрывать и в которые нельзя ничего вложить. Например, тег переноса строки br:

<h1>
  Лев Николаевич Толстой. <br>
  Жизнь и взгляды
</h1>

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

Ещё в ХТМЛ есть комментарии. Это специальный текст, который не будет видно на странице, только в коде. Разработчики используют комментарии в качестве подсказок себе и другим людям, работающим с кодом.

Комментарии пишут внутри конструкции <!-- … -->, это позволяет удобно прятать любые теги — достаточно дописать восклицательный знак и дефисы в начале и конце тега и он превратится в комментарий, пропадёт со страницы, при этом оставшись в коде. На языке разработчиков это называется «закомментить» — спрятать кусок вёрстки.

<p>Автор: Галина Игнатова</p>

<!-- Менять код только с разрешения главреда! -->
<p>Издание «Валенсия», код агента 348187</p>

<!-- Источники, закомментить, когда ссылок нет  -->
<!-- ul>
  <li></li>
  <li></li>
</ul -->

Атрибуты тегов

У каж­дого тега могут быть атри­буты — осо­бые пара­метры для управ­ле­ния его внеш­ним видом и пове­де­нием. Атри­буты запи­сы­вают после назва­ния тега.

Раньше вер­сталь­щики часто исполь­зо­вали атри­буты для оформ­ле­ния эле­мен­тов, но такой под­ход был неудоб­ным и сильно засо­рял ХТМЛ‑код. Посте­пенно от исполь­зо­ва­ния мно­гих атри­бу­тов отказались.

Сегодня вер­сталь­щики исполь­зуют на посто­ян­ной основе лишь несколько атри­бу­тов, со всеми из них мы позна­ко­мимся дальше по ходу книги.

Больше всего атри­бу­тов исполь­зуют в вёрстке форм и полей ввода, там атри­бу­тами настра­и­ва­ется боль­шин­ство осо­бен­но­стей пове­де­ния элементов.

<body>
  <h1 id="pageHeading" class="title">Вход на сайт</h1>

  <form method="GET" action="authorize.php">
    <p>
      <input type="text" placeholder="Логин" autofocus>
    </p>
    <p>
      <input type="password" placeholder="Пароль">
    </p>
    <p>
      <button type="submit" disabled>Войти</button>
    </p>
  </form>

  <p>
    <a href="/restore-password/">Забыли пароль?</a>
  </p>
</body>

Пример кода с большим количеством атрибутов. По названию многих атрибутов легко догадаться, за что они отвечают

Атрибуты тегов

У каждого тега могут быть атрибуты — особые параметры для управления его внешним видом и поведением. Атрибуты записывают после названия тега.

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

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

Больше всего атрибутов используют в вёрстке форм и полей ввода, там атрибутами настраивается большинство особенностей поведения элементов.

<body>
  <h1 id="pageHeading" class="title">Вход на сайт</h1>

  <form method="GET" action="authorize.php">
    <p>
      <input type="text" placeholder="Логин" autofocus>
    </p>
    <p>
      <input type="password" placeholder="Пароль">
    </p>
    <p>
      <button type="submit" disabled>Войти</button>
    </p>
  </form>

  <p>
    <a href="/restore-password/">Забыли пароль?</a>
  </p>
</body>

Пример кода с большим количеством атрибутов. По названию многих атрибутов легко догадаться, за что они отвечают

Даблдекер на знаке. Транспорт Лондона

Даблдекер на знаке. Транспорт Лондона

Даблдекер на знаке. Транспорт Лондона

Даблдекер на знаке. Транспорт Лондона

Даблдекер на знаке. Транспорт Лондона

<img>

<p>
  Даблдекер на знаке.
  <a>
    Транспорт Лондона
  </a>
</p>
<img>

<p>
  Даблдекер на знаке.
  <a href="https://tfl.gov.uk/info-for/suppliers-and-contractors/design-standards">
    Транспорт Лондона
  </a>
</p>
<img src="https://bureau.ru/projects/book-html/files/london-bus.png">

<p>
  Даблдекер на знаке.
  <a href="https://tfl.gov.uk/info-for/suppliers-and-contractors/design-standards">
    Транспорт Лондона
  </a>
</p>
<img src="..."
  width="300px"
  height="80px">

<p>
  Даблдекер на знаке.
  <a href="...">
    Транспорт Лондона
  </a>
</p>
<img src="..."
  width="300px"
  height="80px"
  style="transform: rotate(-5deg) translateY(-3px)">

<p style="font-weight: 100">
  Даблдекер на знаке.
  <a href="...">
    Транспорт Лондона
  </a>
</p>

Свер­стаем кар­тинку и под­пись к ней со ссыл­кой на источ­ник изображения.

Атри­бут href задаёт адрес ссылки.

Чтобы кар­тинка появи­лась, нужно ука­зать атри­бут src.

Чтобы настро­ить раз­меры кар­тинки, исполь­зуют атри­буты width и height.

Самый про­дви­ну­тый атри­бут — style, в нём можно настро­ить что угодно. О нём рас­ска­жем позже.

Даблдекер на знаке. Транспорт Лондона

Даблдекер на знаке. Транспорт Лондона

Даблдекер на знаке. Транспорт Лондона

Даблдекер на знаке. Транспорт Лондона

Даблдекер на знаке. Транспорт Лондона

<img>

<p>
  Даблдекер на знаке.
  <a>
    Транспорт Лондона
  </a>
</p>
<img>

<p>
  Даблдекер на знаке.
  <a href="https://tfl.gov.uk/info-for/suppliers-and-contractors/design-standards">
    Транспорт Лондона
  </a>
</p>
<img src="https://bureau.ru/projects/book-html/files/london-bus.png">

<p>
  Даблдекер на знаке.
  <a href="https://tfl.gov.uk/info-for/suppliers-and-contractors/design-standards">
    Транспорт Лондона
  </a>
</p>
<img src="..."
  width="300px"
  height="80px">

<p>
  Даблдекер на знаке.
  <a href="...">
    Транспорт Лондона
  </a>
</p>
<img src="..."
  width="300px"
  height="80px"
  style="transform: rotate(-5deg) translateY(-3px)">

<p style="font-weight: 100">
  Даблдекер на знаке.
  <a href="...">
    Транспорт Лондона
  </a>
</p>

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

Атрибут href задаёт адрес ссылки.

Чтобы картинка появилась, нужно указать атрибут src.

Чтобы настроить размеры картинки, используют атрибуты width и height.

Самый продвинутый атрибут — style, в нём можно настроить что угодно. О нём расскажем позже.

<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">

    <title>
      Портфолио Остапа Бендера
    </title>
    <!--
      Название страницы.
      Его мы видим в заголовке вкладки
    -->
  </head>

  <body>
    <h1>Портфолио Остапа Бендера</h1>
    <!-- Заголовок первого уровня -->

    <section>
      <!--
        section объединяет элементы в «главу» текста
      -->

      <h2>Кто такой? Чем знаменит?</h2>
      <!-- Заголовок второго уровня -->

      <p class="outstanding">
         Я великий комбинатор,
         идейный борец за денежные знаки.
      </p>
      <!-- Абзац -->

      <p>
         Кроме того, я знаю
         <a href="/projects/400">
           четыреста
           сравнительно честных
           способа отъёма денег</a>.
      </p>
    </section>
    
    <section>
      <h2>Аферы</h2>
      <p>
         В городе Арбатове
         выдал себя за сына
         лейтенанта Шмидта и получил
         от председателя горисполкома
         небольшую материальную помощь.
      </p>
      <!-- И так далее -->
    </section>
  </body>
</html>

Служебные теги

ХТМЛ‑стра­нице нужен обя­за­тель­ный обвес: doctype, html, head и body.

doctype — закли­на­ние, без кото­рого бра­у­зер может криво пока­зать стра­ницу. Это стан­дарт­ный текст, кото­рый меня­ется раз в 20 лет.

head — «голова» стра­ницы. Её содер­жи­мое не отоб­ра­жа­ется напря­мую на стра­нице, но исполь­зу­ется в спе­ци­аль­ных местах: в заго­ловке окна стра­ницы, в заклад­ках, в выдаче поис­ко­ви­ков и соцсетях.

body — «тело», само содер­жа­ние стра­ницы, кото­рое мы уви­дим в браузере.

Чтобы посмотреть код любой страницы в браузере, щёлкните правой кнопкой мыши и выберите «Просмотр кода страницы» (View Page Source). Либо с клавиатуры — Ctrl+U или ⌘+Option+U

<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">

    <title>
      Портфолио Остапа Бендера
    </title>
    <!--
      Название страницы.
      Его мы видим в заголовке вкладки
    -->
  </head>

  <body>
    <h1>Портфолио Остапа Бендера</h1>
    <!-- Заголовок первого уровня -->

    <section>
      <!--
        section объединяет элементы в «главу» текста
      -->

      <h2>Кто такой? Чем знаменит?</h2>
      <!-- Заголовок второго уровня -->

      <p class="outstanding">
         Я великий комбинатор,
         идейный борец за денежные знаки.
      </p>
      <!-- Абзац -->

      <p>
         Кроме того, я знаю
         <a href="/projects/400">
           четыреста
           сравнительно честных
           способа отъёма денег</a>.
      </p>
    </section>
    
    <section>
      <h2>Аферы</h2>
      <p>
         В городе Арбатове
         выдал себя за сына
         лейтенанта Шмидта и получил
         от председателя горисполкома
         небольшую материальную помощь.
      </p>
      <!-- И так далее -->
    </section>
  </body>
</html>

Чтобы посмотреть код любой страницы в браузере, щёлкните правой кнопкой мыши и выберите «Просмотр кода страницы» (View Page Source). Либо с клавиатуры — Ctrl+U или ⌘+Option+U

Служебные теги

ХТМЛ‑странице нужен обязательный обвес: doctype, html, head и body.

doctype — заклинание, без которого браузер может криво показать страницу. Это стандартный текст, который меняется раз в 20 лет.

head — «голова» страницы. Её содержимое не отображается напрямую на странице, но используется в специальных местах: в заголовке окна страницы, в закладках, в выдаче поисковиков и соцсетях.

body — «тело», само содержание страницы, которое мы увидим в браузере.

Знакомство с ЦСС

ХТМЛ — это текст, кото­рый опи­сы­вает содер­жи­мое стра­ницы, а ЦСС — это текст, кото­рый опи­сы­вает внеш­ний вид этого содер­жи­мого. ХТМЛ отве­чает на вопрос что пока­зать, а ЦСС — как показать.

ЦСС‑код можно писать прямо внутри ХТМЛ‑файла в осо­бом теге style. Сам код нагляд­ный, его легко читать и пони­мать. Сна­чала пишут, какой эле­мент сти­ли­зо­вать, затем в фигур­ных скоб­ках — свой­ства эле­мента, как он дол­жен выглядеть.

Напри­мер, так можно задать заго­ловку и абза­цам цвет, шрифт, кегль и интер­ли­ньяж. Назва­ния свойств и их зна­че­ния гово­рят сами за себя:

<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">
    <title>Прекрасный Хайош-Байа, Венгрия</title>

    <!-- 
      Все стили из тега style применятся к странице 
    -->
    <style>
      h1 {
        font-family: Helvetica;
        font-size: 48px;
        line-height: 52px;
        color: rgb(6 85 128);
      }

      p {
        font-size: 20px;
        line-height: 24px;
        font-family: Times;
      }
      
      /* 
        В ЦСС куча свойств, с большинством мы познакомимся в книге. Кстати, так выглядит ЦСС-комментарий — текст между слешей и звёздочек не видно на странице, только в коде 
      */
    </style>
  </head>

  <body>
    <h1>Прекрасный Хайош-Байа, Венгрия</h1>
    <p>Горизонт ожидания…</p>
    <p>Шиллер утверждал…</p>
  </body> 
</html>

Попробуйте скопировать код и поэкспериментировать со свойствами. Например, изменить кегли, шрифты и цвета

Знакомство с ЦСС

ХТМЛ — это текст, который описывает содержимое страницы, а ЦСС — это текст, который описывает внешний вид этого содержимого. ХТМЛ отвечает на вопрос что показать, а ЦСС — как показать.

ЦСС‑код можно писать прямо внутри ХТМЛ‑файла в особом теге style. Сам код наглядный, его легко читать и понимать. Сначала пишут, какой элемент стилизовать, затем в фигурных скобках — свойства элемента, как он должен выглядеть.

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

<!doctype html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">
    <title>Прекрасный Хайош-Байа, Венгрия</title>

    <!-- 
      Все стили из тега style применятся к странице 
    -->
    <style>
      h1 {
        font-family: Helvetica;
        font-size: 48px;
        line-height: 52px;
        color: rgb(6 85 128);
      }

      p {
        font-size: 20px;
        line-height: 24px;
        font-family: Times;
      }
      
      /* 
        В ЦСС куча свойств, с большинством мы познакомимся в книге. Кстати, так выглядит ЦСС-комментарий — текст между слешей и звёздочек не видно на странице, только в коде 
      */
    </style>
  </head>

  <body>
    <h1>Прекрасный Хайош-Байа, Венгрия</h1>
    <p>Горизонт ожидания…</p>
    <p>Шиллер утверждал…</p>
  </body> 
</html>

Попробуйте скопировать код и поэкспериментировать со свойствами. Например, изменить кегли, шрифты и цвета

Выборочная стилизация, классы

Если сти­ли­зо­вать теги по назва­нию, стили при­ме­нятся ко всем таким тегам. Если один раз ука­зать цвет для p, все абзацы на стра­нице изме­нят цвет:

<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">
    <title>Ледник Акселя Хейберга</title>
    <style>
      p {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1>Ледник Акселя Хейберга</h1>
    <p>…</p>
    <p>…</p>
    <p>…</p>
    <p>…</p>
  </body> 
</html>

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

Чтобы точ­нее ука­зы­вать на нуж­ные эле­менты, исполь­зуют классы — иден­ти­фи­ка­торы, к кото­рым при­вя­заны стили. В ХМТЛ класс пишут в одно­имён­ном атри­буте, затем в ЦСС к классу при­вя­зы­вают стили. Эти стили при­ме­нятся ко всем эле­мен­там стра­ницы с под­хо­дя­щим клас­сом. В ЦСС имя класса пишут через точку, а в ХТМЛ — без точки:

<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">
    <title>Ледник Акселя Хейберга</title>
    <style>
      .highlighted {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1>Ледник Акселя Хейберга</h1>
    <p>…</p>
    <p class="highlighted">…</p>
    <p>…</p>
    <p class="highlighted">…</p>
  </body> 
</html>

Классы удобно исполь­зо­вать повторно. Один класс можно пове­сить на сколько угодно тегов, при­ме­нив стили ко всем из них. Часто делают уни­вер­саль­ные слу­жеб­ные классы, чтобы исполь­зо­вать их в раз­ных местах сайта. Напри­мер, класс для выде­ле­ния цве­том, как в при­мере выше.

Выборочная стилизация, классы

Если стилизовать теги по названию, стили применятся ко всем таким тегам. Если один раз указать цвет для p, все абзацы на странице изменят цвет:

<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">
    <title>Ледник Акселя Хейберга</title>
    <style>
      p {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1>Ледник Акселя Хейберга</h1>
    <p>…</p>
    <p>…</p>
    <p>…</p>
    <p>…</p>
  </body> 
</html>

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

Чтобы точнее указывать на нужные элементы, используют классы — идентификаторы, к которым привязаны стили. В ХМТЛ класс пишут в одноимённом атрибуте, затем в ЦСС к классу привязывают стили. Эти стили применятся ко всем элементам страницы с подходящим классом. В ЦСС имя класса пишут через точку, а в ХТМЛ — без точки:

<!DOCTYPE html>
<html lang="ru">
  <head>
    <meta charset="UTF-8">
    <title>Ледник Акселя Хейберга</title>
    <style>
      .highlighted {
        color: red;
      }
    </style>
  </head>
  <body>
    <h1>Ледник Акселя Хейберга</h1>
    <p>…</p>
    <p class="highlighted">…</p>
    <p>…</p>
    <p class="highlighted">…</p>
  </body> 
</html>

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

Лайфхак: как называть классы

Чтобы код было проще читать и пони­мать, имена клас­сов делают наглядными.

В име­нах избе­гают слу­чай­ных набо­ров букв, аббре­ви­а­тур, сокра­ще­ний и всего, что может как‑либо запутать.

Классы не при­нято назы­вать по‑рус­ски лати­ни­цей. Такие имена тяжело читать, а неко­то­рые рус­ские буквы можно напи­сать лати­ни­цей по‑раз­ному, что уве­ли­чи­вает веро­ят­ность ошибок.

/* Плохо */
.t { }
.txthl { }
.informaciya { }
.informacia { }
.informacija { }

/* Лучше */
.title { }
.highlight { }
.info { }

Если имя состоит из несколь­ких слов, лучше визу­ально их раз­де­лить. Два самых попу­ляр­ных стиля: раз­де­лять слова дефи­сами или заглав­ными буквами.

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

/* Каша, тяжело читать */
.pagetitle { }
.pageauthor { }

/* Структура, легко читать */
.page-title { }
.page-author { }

.pageTitle { }
.pageAuthor { }

Стиль‑через‑дефис называют кебабом, потому что похоже на мясо на шампуре. А стильЧерезЗаглавные — кэмелом, потому что похоже на горбы верблюда.

Совет. Чтобы в текстовом редакторе перемещаться по отдельным словам в именах с дефисами, используйте Option или Alt со стрелками влево и вправо. Чтобы выделять слова, дополнительно зажимайте Shift

Лайфхак: как называть классы

Чтобы код было проще читать и понимать, имена классов делают наглядными.

В именах избегают случайных наборов букв, аббревиатур, сокращений и всего, что может как‑либо запутать.

Классы не принято называть по‑русски латиницей. Такие имена тяжело читать, а некоторые русские буквы можно написать латиницей по‑разному, что увеличивает вероятность ошибок.

/* Плохо */
.t { }
.txthl { }
.informaciya { }
.informacia { }
.informacija { }

/* Лучше */
.title { }
.highlight { }
.info { }

Если имя состоит из нескольких слов, лучше визуально их разделить. Два самых популярных стиля: разделять слова дефисами или заглавными буквами.

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

/* Каша, тяжело читать */
.pagetitle { }
.pageauthor { }

/* Структура, легко читать */
.page-title { }
.page-author { }

.pageTitle { }
.pageAuthor { }

Стиль‑через‑дефис называют кебабом, потому что похоже на мясо на шампуре. А стильЧерезЗаглавные — кэмелом, потому что похоже на горбы верблюда.

Совет. Чтобы в текстовом редакторе перемещаться по отдельным словам в именах с дефисами, используйте Option или Alt со стрелками влево и вправо. Чтобы выделять слова, дополнительно зажимайте Shift

Где хранить стили

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

Когда сти­лей много, удоб­нее выне­сти их в отдель­ный файл и рабо­тать с ним в отдель­ном окне редактора.

Файл со сти­лями имеет рас­ши­ре­ние .css. Обычно его назы­вают style.css и кла­дут рядом с ХТМЛ‑стра­ни­цей, это самый про­стой спо­соб для неболь­ших сай­тов. Файл под­клю­чают к стра­нице тегом link, все стили из под­клю­чён­ного файла при­ме­нятся к странице.

Файл сти­лей можно под­клю­чить сразу к несколь­ким ХТМЛ‑стра­ни­цам и управ­лять их сти­лями из одного места: поме­няли отступы у абза­цев в style.css, отступы поме­ня­лись на всех страницах.

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

index.html

<!-- … -->
  <head>
    <title>Моя веб-страница</title>

    <style>
      body {
        font-size: 16px;
        line-height: 18px;
      }

      h1 {
        font-size: 48px;
        line-height: 52px;
        font-family: Helvetica;
      }
    </style>
  </head>
<!-- … -->

index.html

<!-- … -->
  <head>
    <title>Моя веб-страница</title>

    <!-- Вместо style подключаем лежащий рядом файл -->
    <link rel="stylesheet" href="style.css">
  </head>
<!-- … -->

style.css

/* Стили, которые раньше были в теге style */
body {
  font-size: 16px;
  line-height: 18px;
}

h1 {
  font-size: 48px;
  line-height: 52px;
  font-family: Helvetica;
}

index.html

<!-- … -->
  <head>
    <title>Моя веб-страница</title>

    <style>
      body {
        font-size: 16px;
        line-height: 18px;
      }

      h1 {
        font-size: 48px;
        line-height: 52px;
        font-family: Helvetica;
      }
    </style>
  </head>
<!-- … -->

index.html

<!-- … -->
  <head>
    <title>Моя веб-страница</title>

    <!-- Вместо style подключаем лежащий рядом файл -->
    <link rel="stylesheet" href="style.css">
  </head>
<!-- … -->

style.css

/* Стили, которые раньше были в теге style */
body {
  font-size: 16px;
  line-height: 18px;
}

h1 {
  font-size: 48px;
  line-height: 52px;
  font-family: Helvetica;
}

Где хранить стили

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

Когда стилей много, удобнее вынести их в отдельный файл и работать с ним в отдельном окне редактора.

Файл со стилями имеет расширение .css. Обычно его называют style.css и кладут рядом с ХТМЛ‑страницей, это самый простой способ для небольших сайтов. Файл подключают к странице тегом link, все стили из подключённого файла применятся к странице.

Файл стилей можно подключить сразу к нескольким ХТМЛ‑страницам и управлять их стилями из одного места: поменяли отступы у абзацев в style.css, отступы поменялись на всех страницах.

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

Наследование стилей

Теги насле­дуют стили от роди­те­лей — тегов, внутри кото­рых нахо­дятся. Насле­до­ва­ние сти­лей поз­во­ляет писать меньше кода, избе­гать повто­ров сти­лей, после­до­ва­тельно сти­ли­зо­вать стра­ницу от общего к частному.

Тег body — роди­тель для вло­жен­ных тегов заго­ловка и абзацев:

<body>
  <h1>Джефф Раскин — Интерфейс</h1>
  <p>…</p>
  <p>…</p>
</body>

Если в ЦСС задать шрифт и цвет тек­ста для body, вло­жен­ные теги уна­сле­дуют эти свойства:

body {
  font-family: Arial;
  color: red;
}

Уна­сле­до­ван­ные свой­ства можно переопределять:

body {
  font-family: Arial;
  color: red;
}

p {
  color: blue;
}

Наследование стилей

Теги наследуют стили от родителей — тегов, внутри которых находятся. Наследование стилей позволяет писать меньше кода, избегать повторов стилей, последовательно стилизовать страницу от общего к частному.

Тег body — родитель для вложенных тегов заголовка и абзацев:

<body>
  <h1>Джефф Раскин — Интерфейс</h1>
  <p>…</p>
  <p>…</p>
</body>

Если в ЦСС задать шрифт и цвет текста для body, вложенные теги унаследуют эти свойства:

body {
  font-family: Arial;
  color: red;
}

Унаследованные свойства можно переопределять:

body {
  font-family: Arial;
  color: red;
}

p {
  color: blue;
}

Если с эле­мен­том свя­заны несколько набо­ров сти­лей, они допол­нят друг друга. Эта осо­бен­ность помо­гает меньше повто­ряться в коде, писать уни­вер­саль­ные классы и исполь­зо­вать их в раз­ных местах страницы.

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

<p>…</p>
<p class="liftOut">…</p>
p {
  font-family: Helvetica, sans-serif;
}

.liftOut {
  font-size: 125%;
  color: red;
}

Если с элементом связаны несколько наборов стилей, они дополнят друг друга. Эта особенность помогает меньше повторяться в коде, писать универсальные классы и использовать их в разных местах страницы.

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

<p>…</p>
<p class="liftOut">…</p>
p {
  font-family: Helvetica, sans-serif;
}

.liftOut {
  font-size: 125%;
  color: red;
}

Конфликты стилей

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

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

Чем ниже стили напи­саны в коде, тем они важ­нее при про­чих равных:

<p>…</p>
p {
  color: red;
}

/*
  Написанные ниже стили абзаца
  перебьют написанные выше
*/
p {
  color: blue;
}

Стили для клас­сов важ­нее сти­лей для тегов:

<p class="large">.…</p>
/* 
  Цвет из класса перебьёт цвет из тега, 
  даже если те стоят ниже в коде
*/
.large {
  color: crimson;
}

p {
  color: blue;
}

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

Если эле­мент вдруг выгля­дит не так, как он опи­сан в сти­лях, — пер­вым делом убе­ди­тесь, что к нему не при­ме­ня­ются какие‑ни­будь допол­ни­тель­ные стили из дру­гих мест. Это можно легко сде­лать при помощи веб‑инспек­тора, мы изу­чим его в сле­ду­ю­щих главах.

Важность свойств на языке разработчиков называется специфичностью.

Конфликты стилей

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

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

Чем ниже стили написаны в коде, тем они важнее при прочих равных:

<p>…</p>
p {
  color: red;
}

/*
  Написанные ниже стили абзаца
  перебьют написанные выше
*/
p {
  color: blue;
}

Стили для классов важнее стилей для тегов:

<p class="large">.…</p>
/* 
  Цвет из класса перебьёт цвет из тега, 
  даже если те стоят ниже в коде
*/
.large {
  color: crimson;
}

p {
  color: blue;
}

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

Если элемент вдруг выглядит не так, как он описан в стилях, — первым делом убедитесь, что к нему не применяются какие‑нибудь дополнительные стили из других мест. Это можно легко сделать при помощи веб‑инспектора, мы изучим его в следующих главах.

Блочные и строчные элементы

В ХТМЛ есть два основ­ных типа элементов.

Блочные элементы зани­мают всю доступ­ную ширину роди­теля и по умол­ча­нию рас­по­ла­га­ются как этажи друг над дру­гом, но могут рас­кла­ды­ваться и по сетке. При­меры блоч­ных эле­мен­тов: заго­ловки, абзацы и списки.

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

Можно изме­нить тип эле­мента: заста­вить слово вести себя как блок, а заго­ло­вок — как слово в строке. За это отве­чает свой­ство display: зна­че­ние block делает эле­мент блоч­ным, а inline — строч­ным. Бывают и дру­гие зна­че­ния display, мы позна­ко­мимся с ними дальше в книге.

Блочные и строчные элементы

В ХТМЛ есть два основных типа элементов.

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

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

Можно изменить тип элемента: заставить слово вести себя как блок, а заголовок — как слово в строке. За это отвечает свойство display: значение block делает элемент блочным, а inline — строчным. Бывают и другие значения display, мы познакомимся с ними дальше в книге.

Отступы

У эле­мен­тов бывают внут­рен­ние и внеш­ние отступы.

Внутренние отступы от гра­ниц эле­мента до содер­жи­мого назы­вают «падин­гами». Их задают свой­ством padding для раз­ных сторон:

p {
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 20px;
  padding-bottom: 30px;
}

Фон эле­мента рас­про­стра­ня­ется на падинги, это удобно при вёрстке кар­то­чек и плашек:

p {
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 20px;
  padding-bottom: 30px;
  font-size: 120%;
  background: #f3f3f3;
}

Внешние отступы от гра­ниц эле­мента до сосед­них эле­мен­тов назы­вают «мар­жи­нами». Их задают свой­ством margin для раз­ных сторон:

p {
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 20px;
  padding-bottom: 30px;
  font-size: 120%;
  background: #f3f3f3;
  margin-bottom: 32px;
}

Вер­ти­каль­ные мар­жины могут быть только у блоч­ных элементов.

Мар­жины и падинги можно и нужно комбинировать.

Поме­стим абзацы из при­мера и заго­ло­вок внутрь кар­точки с под­лож­кой. Чтобы не зада­вать каж­дому эле­менту отступы до края кар­точки, ука­жем внут­рен­ние отступы самой кар­точке. А эле­мен­там ука­жем только вер­ти­каль­ные отступы друг от друга:

<div class="card">
  <h3>How doth the little crocodile</h3>
  <p>...</p>
  <p>...</p>
</div>
.card {
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 20px;
  padding-bottom: 30px;
  background-color: #f3f3f3;
}

h3 {
  margin-bottom: 24px;
}

p {
  margin-top: 0;
  margin-bottom: 18px;
}

/* Обнуляем нижний отступ для последнего абзаца */
p:last-child {
  margin-bottom: 0;
}

Нет стро­гих пра­вил и тре­бо­ва­ний, когда исполь­зо­вать падинг, а когда мар­жин. Но лучше исполь­зо­вать свой­ства по их назна­че­нию: для внут­рен­них и внеш­них отсту­пов соот­вет­ственно. Такую вёрстку будет проще пони­мать дру­гим разработчикам.

Отступы

У элементов бывают внутренние и внешние отступы.

Внутренние отступы от границ элемента до содержимого называют «падингами». Их задают свойством padding для разных сторон:

p {
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 20px;
  padding-bottom: 30px;
}

Фон элемента распространяется на падинги, это удобно при вёрстке карточек и плашек:

p {
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 20px;
  padding-bottom: 30px;
  font-size: 120%;
  background: #f3f3f3;
}

Внешние отступы от границ элемента до соседних элементов называют «маржинами». Их задают свойством margin для разных сторон:

p {
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 20px;
  padding-bottom: 30px;
  font-size: 120%;
  background: #f3f3f3;
  margin-bottom: 32px;
}

Вертикальные маржины могут быть только у блочных элементов.

Маржины и падинги можно и нужно комбинировать.

Поместим абзацы из примера и заголовок внутрь карточки с подложкой. Чтобы не задавать каждому элементу отступы до края карточки, укажем внутренние отступы самой карточке. А элементам укажем только вертикальные отступы друг от друга:

<div class="card">
  <h3>How doth the little crocodile</h3>
  <p>...</p>
  <p>...</p>
</div>
.card {
  padding-left: 10px;
  padding-right: 10px;
  padding-top: 20px;
  padding-bottom: 30px;
  background-color: #f3f3f3;
}

h3 {
  margin-bottom: 24px;
}

p {
  margin-top: 0;
  margin-bottom: 18px;
}

/* Обнуляем нижний отступ для последнего абзаца */
p:last-child {
  margin-bottom: 0;
}

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

Лайфхак: краткая запись

У неко­то­рых ЦСС‑свойств есть крат­кая запись. Это спо­соб опти­ми­зи­ро­вать и сокра­тить код, заме­нить несколько свойств одним.

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

.example {
  padding-top: 10px;
  padding-right: 5px;
  padding-bottom: 20px;
  padding-left: 5px;

  padding: 10px 5px 20px 5px; /* Верх, право, низ, лево */

  padding: 10px 5px 20px; /* Верх, право-лево, низ */

  padding: 10px 5px; /* Низ-верх, право-лево */

  padding: 5px; /* Все отступы */
}

Можно опи­сать базо­вые стили крат­кой запи­сью, а в осо­бых слу­чаях ука­зать отличия.

p {
  margin: 0 18px 24px 0;
}

.lead {
  margin-bottom: 24px; /* Меняем только нижний маржин */
}

.noIndent {
  margin: 0; /* Меняем краткую запись целиком, все маржины обнулятся */
}

/* И наоборот: краткая запись переопределит все маржины */
h1 {
  margin-top: 8px;
  margin-bottom: 18px;
}

.pageTitle {
  margin: 27px 0 36px;
}

Лайфхак: краткая запись

У некоторых ЦСС‑свойств есть краткая запись. Это способ оптимизировать и сократить код, заменить несколько свойств одним.

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

.example {
  padding-top: 10px;
  padding-right: 5px;
  padding-bottom: 20px;
  padding-left: 5px;

  padding: 10px 5px 20px 5px; /* Верх, право, низ, лево */

  padding: 10px 5px 20px; /* Верх, право-лево, низ */

  padding: 10px 5px; /* Низ-верх, право-лево */

  padding: 5px; /* Все отступы */
}

Можно описать базовые стили краткой записью, а в особых случаях указать отличия.

p {
  margin: 0 18px 24px 0;
}

.lead {
  margin-bottom: 24px; /* Меняем только нижний маржин */
}

.noIndent {
  margin: 0; /* Меняем краткую запись целиком, все маржины обнулятся */
}

/* И наоборот: краткая запись переопределит все маржины */
h1 {
  margin-top: 8px;
  margin-bottom: 18px;
}

.pageTitle {
  margin: 27px 0 36px;
}

Веб‑инспектор

В Хроме

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

Проще всего это сде­лать в бра­у­зере с помо­щью веб‑инспек­тора — встро­ен­ного набора инстру­мен­тов для разработчиков.

В Сафари

Открыть его можно с помо­щью соче­та­ния ⌘+Option+I на Маке и Ctrl+Shift+I или F12 в дру­гих ОС.

Первая вкладка любого веб‑инспектора — Элементы, основной инструмент для просмотра и редактирования кода страницы. Обычно эта вкладка разделена пополам: слева разметка, справа стили выбранного элемента

Веб‑инспектор

В Хроме

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

Проще всего это сделать в браузере с помощью веб‑инспектора — встроенного набора инструментов для разработчиков.

В Сафари

Открыть его можно с помощью сочетания ⌘+Option+I на Маке и Ctrl+Shift+I или F12 в других ОС.

Первая вкладка любого веб‑инспектора — Элементы, основной инструмент для просмотра и редактирования кода страницы. Обычно эта вкладка разделена пополам: слева разметка, справа стили выбранного элемента

Отредактируем портфолио, поменяв текст заголовка с помощью веб‑инспектора

В других браузерах пункт меню может называться «Проверить элемент»

Здесь и дальше мы будем использовать веб‑инспектор Гугль Хрома

В других браузерах есть отличия в деталях, но общий подход тот же

Изменения сохранились в браузере. Если обновить страницу, они исчезнут

Редактирование разметки. Чтобы выбрать эле­мент для редак­ти­ро­ва­ния, клик­нем на нём пра­вой кноп­кой и выбе­рем в кон­текст­ном меню «Инспектировать».

Откро­ется панель веб‑инспек­тора: слева эле­менты, справа их свойства.

Чтобы перейти в режим редак­ти­ро­ва­ния, два­жды клик­нем по тексту.

Затем изме­ним текст и нажмём «энтер», чтобы при­ме­нить изменения.

Отредактируем портфолио, поменяв текст заголовка с помощью веб‑инспектора

В других браузерах пункт меню может называться «Проверить элемент»

Здесь и дальше мы будем использовать веб‑инспектор Гугль Хрома

В других браузерах есть отличия в деталях, но общий подход тот же

Изменения сохранились в браузере. Если обновить страницу, они исчезнут

Редактирование разметки. Чтобы выбрать элемент для редактирования, кликнем на нём правой кнопкой и выберем в контекстном меню «Инспектировать».

Откроется панель веб‑инспектора: слева элементы, справа их свойства.

Чтобы перейти в режим редактирования, дважды кликнем по тексту.

Затем изменим текст и нажмём «энтер», чтобы применить изменения.

Посмот­рим, как выгля­дит кнопка в раз­ных состо­я­ниях. Два­жды клик­нем по списку клас­сов кнопки.

Поме­няем класс btn‑bg на btn‑sm и нажмём «энтер», чтобы при­ме­нить изменения.

Чтобы посмот­реть, как выгля­дит нажа­тая кнопка, клик­нем пра­вой кноп­кой и выбе­рем Force State — :active.

Новое состо­я­ние при­ме­нится к кнопке, соот­вет­ству­ю­щие стили появятся в панели справа.

Поэкспериментируем с кнопкой и её состояниями на промостранице Бутстрапа

В Бустрапе используют комбинации классов для модификации элементов

В нашем случае, btn — базовый класс, а btn‑sm и btn‑lg — модификаторы размера

Здесь же можно включить ховер, фокус или «посещённость» у ссылки

Оранжевый кружок отмечает элементы с изменёнными вручную состояниями

Поэкспериментируем с кнопкой и её состояниями на промостранице Бутстрапа

В Бустрапе используют комбинации классов для модификации элементов

В нашем случае, btn — базовый класс, а btn‑sm и btn‑lg — модификаторы размера

Здесь же можно включить ховер, фокус или «посещённость» у ссылки

Оранжевый кружок отмечает элементы с изменёнными вручную состояниями

Посмотрим, как выглядит кнопка в разных состояниях. Дважды кликнем по списку классов кнопки.

Поменяем класс btn‑bg на btn‑sm и нажмём «энтер», чтобы применить изменения.

Чтобы посмотреть, как выглядит нажатая кнопка, кликнем правой кнопкой и выберем Force State — :active.

Новое состояние применится к кнопке, соответствующие стили появятся в панели справа.

Поэкспериментируем с формой отправки вопроса в советы бюро

Бывает, кнопки и поля блокируются отдельно или в родительском <fieldset>

Атрибут disabled блокирует кнопки, инпуты, выпадайки и текстареи

Если атрибут disabled стоит у <fieldset>, то блокируются все вложенные элементы

Поэтому хорошие программисты дополнительно проверяют данные на сервере

Раз­бло­ки­руем отклю­чён­ную форму. Клик­нем по забло­ки­ро­ван­ной кнопке пра­вой кноп­кой и выбе­рем в кон­текст­ном меню «Инспектировать».

Откро­ется панель веб‑инспек­тора. Най­дём в ней забло­ки­ро­ван­ный эле­мент с атри­бу­том disabled.

Чтобы перейти в режим редак­ти­ро­ва­ния атри­бута, два­жды клик­нем по нему.

Затем уда­лим атри­бут и его зна­че­ние и нажмём «энтер», чтобы при­ме­нить изменения.

Поэкспериментируем с формой отправки вопроса в советы бюро

Бывает, кнопки и поля блокируются отдельно или в родительском <fieldset>

Атрибут disabled блокирует кнопки, инпуты, выпадайки и текстареи

Если атрибут disabled стоит у <fieldset>, то блокируются все вложенные элементы

Поэтому хорошие программисты дополнительно проверяют данные на сервере

Разблокируем отключённую форму. Кликнем по заблокированной кнопке правой кнопкой и выберем в контекстном меню «Инспектировать».

Откроется панель веб‑инспектора. Найдём в ней заблокированный элемент с атрибутом disabled.

Чтобы перейти в режим редактирования атрибута, дважды кликнем по нему.

Затем удалим атрибут и его значение и нажмём «энтер», чтобы применить изменения.

Скроем эле­мент. Для этого клик­нем по нему пра­вой кноп­кой и выбе­рем Hide Element.

Эле­мент исчез, но всё ещё зани­мает место на странице.

Чтобы пол­но­стью уда­лить эле­мент, клик­нем по нему пра­вой кноп­кой и выбе­рем Delete Element.

Эле­мент исчез со стра­ницы и из веб‑инспектора.

Чтобы отме­нить послед­нее дей­ствие и вер­нуть эле­мент, нужно нажать ⌘+Z или Ctrl+Z.

Отредактируем портфолио, скрыв или убрав заголовок второго уровня

Оранжевые поля показывают внешние отступы элемента, маржины

Чтобы скрыть элемент, веб‑инспектор добавляет ему visibility: hidden

Элемент удалился только в браузере. Если обновить страницу, он вернётся

Удаление элемента веб‑инспектором похоже на действие display: none

Отредактируем портфолио, скрыв или убрав заголовок второго уровня

Оранжевые поля показывают внешние отступы элемента, маржины

Чтобы скрыть элемент, веб‑инспектор добавляет ему visibility: hidden

Элемент удалился только в браузере. Если обновить страницу, он вернётся

Удаление элемента веб‑инспектором похоже на действие display: none

Скроем элемент. Для этого кликнем по нему правой кнопкой и выберем Hide Element.

Элемент исчез, но всё ещё занимает место на странице.

Чтобы полностью удалить элемент, кликнем по нему правой кнопкой и выберем Delete Element.

Элемент исчез со страницы и из веб‑инспектора.

Чтобы отменить последнее действие и вернуть элемент, нужно нажать ⌘+Z или Ctrl+Z.

user agent stylesheet — это стили элементов по умолчанию, зашитые в браузер

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

Как видите, браузер просто дописывает новые стили в атрибут style

Зачёркнутые правила либо отключены, либо переопределены где‑то ещё

В веб‑инспекторе Хрома цифровые значения также можно изменять прокруткой

Поме­няем стили заго­ловка. Справа пока­заны стили выбран­ного эле­мента. Чтобы доба­вить сти­лей, клик­нем в element.style.

Панель со сти­лями перей­дёт в режим редак­ти­ро­ва­ния. Допи­шем стили, ука­зав кегль, шрифт и отступы заголовка.

Чтобы отклю­чить свой­ство, клик­нем по чек­боксу возле него.

Чтобы поме­нять зна­че­ние «на глаз», клик­нем в него и пона­жи­маем кла­виши‑стрелки ↑↓. Рабо­тает как в Фото­шопе: с зажа­тым ⌘ уве­ли­чи­вает или умень­шает зна­че­ние на сотни, с Shift — на десятки, с Option — на одну десятую.

user agent stylesheet — это стили элементов по умолчанию, зашитые в браузер

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

Как видите, браузер просто дописывает новые стили в атрибут style

Зачёркнутые правила либо отключены, либо переопределены где‑то ещё

В веб‑инспекторе Хрома цифровые значения также можно изменять прокруткой

Поменяем стили заголовка. Справа показаны стили выбранного элемента. Чтобы добавить стилей, кликнем в element.style.

Панель со стилями перейдёт в режим редактирования. Допишем стили, указав кегль, шрифт и отступы заголовка.

Чтобы отключить свойство, кликнем по чекбоксу возле него.

Чтобы поменять значение «на глаз», кликнем в него и понажимаем клавиши‑стрелки ↑↓. Работает как в Фотошопе: с зажатым ⌘ увеличивает или уменьшает значение на сотни, с Shift — на десятки, с Option — на одну десятую.

Отладка мобильной версии. Чтобы посмот­реть, как стра­ница выгля­дит на раз­ных экра­нах и мобиль­ных устрой­ствах, исполь­зуют отлад­чики мобиль­ной версии.

В Хроме отлад­чик спря­тан за кноп­кой Toggle Device Toolbar в веб‑инспек­торе. В Сафари — это отдель­ный режим, Responsive Design Mode.

Отлад­чик мобиль­ной вер­сии в Сафари откры­ва­ется с помо­щью ⌘+Control+R. В Хроме — с помо­щью ⌘+Option+I и после­ду­ю­щего ⌘+Shift+M.

Что дальше

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

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

В Хроме отладчик спрятан за кнопкой Toggle Device Toolbar в веб‑инспекторе. В Сафари — это отдельный режим, Responsive Design Mode.

Отладчик мобильной версии в Сафари открывается с помощью ⌘+Control+R. В Хроме — с помощью ⌘+Option+I и последующего ⌘+Shift+M.

Что дальше

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

1
Модули

1
Модули

Текстовые модули

Чтобы свер­стать тек­сто­вый модуль, нужно задать шрифт, настро­ить кегль, интер­ли­ньяж, начер­та­ние и вырав­ни­ва­ние. За шрифт отве­чает ЦСС‑свой­ство font-family, за кегль и интер­ли­ньяж — font-size и line-height. Раз­бе­рём их подробнее.

The only easy day
was yesterday

Текстовые модули

Чтобы сверстать текстовый модуль, нужно задать шрифт, настроить кегль, интерлиньяж, начертание и выравнивание. За шрифт отвечает ЦСС‑свойство font-family, за кегль и интерлиньяж — font-size и line-height. Разберём их подробнее.

The only easy day
was yesterday

С помо­щью font-family бра­у­зер пони­мает, какой шрифт исполь­зо­вать для тек­ста элемента:

h3 {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

p {
  font-family: Georgia, Times, Times New Roman, serif;
}

Мы ука­зы­ваем несколько шриф­тов про запас. Если на ком­пью­тере не уста­нов­лен шрифт, если он не загру­зился или в нём нет нуж­ной буквы, бра­у­зер попро­бует сле­ду­ю­щий шрифт из списка.

В конце списка ука­зы­ваем клю­че­вое слово, опи­сы­ва­ю­щее семей­ство шрифта: serif — с засеч­ками, sans‑serif — без засе­чек, monospace — моно­ши­рин­ный. Если бра­у­зер добе­рётся до него, то возь­мёт систем­ный шрифт соот­вет­ству­ю­щего семейства.

Чтобы бра­у­зер мог исполь­зо­вать ука­зан­ный шрифт на стра­нице, этот шрифт дол­жен быть либо уже уста­нов­лен в опе­ра­ци­он­ной системе, либо явно под­клю­чён к стра­нице. Вхо­дя­щие в стан­дарт­ный набор опе­ра­ци­он­ной системы шрифты назы­вают веб‑без­опас­ными: ука­зы­ваем их в font-family и всё рабо­тает, потому что они уже уста­нов­лены у 99% поль­зо­ва­те­лей. К таким шриф­там отно­сятся, напри­мер, Verdana, Arial, Georgia и Times New Roman.

Глава третья

За год до того как Пани­ков­ский нару­шил кон­вен­цию, про­ник­нув в чужой экс­плу­а­та­ци­он­ный уча­сток, в городе Арба­тове появился пер­вый автомобиль.

Глава третья

За год до того как Паниковский нарушил конвенцию, проникнув в чужой эксплуатационный участок, в городе Арбатове появился первый автомобиль.

С помощью font-family браузер понимает, какой шрифт использовать для текста элемента:

h3 {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

p {
  font-family: Georgia, Times, Times New Roman, serif;
}

Мы указываем несколько шрифтов про запас. Если на компьютере не установлен шрифт, если он не загрузился или в нём нет нужной буквы, браузер попробует следующий шрифт из списка.

В конце списка указываем ключевое слово, описывающее семейство шрифта: serif — с засечками, sans‑serif — без засечек, monospace — моноширинный. Если браузер доберётся до него, то возьмёт системный шрифт соответствующего семейства.

Чтобы браузер мог использовать указанный шрифт на странице, этот шрифт должен быть либо уже установлен в операционной системе, либо явно подключён к странице. Входящие в стандартный набор операционной системы шрифты называют веб‑безопасными: указываем их в font-family и всё работает, потому что они уже установлены у 99% пользователей. К таким шрифтам относятся, например, Verdana, Arial, Georgia и Times New Roman.

Под­бе­рём шрифты для поста в блоге, исполь­зуя веб‑без­опас­ные Helvetica и Georgia. Нач­нём с разметки:

<article>
  <h1>Когда использовать double вместо instance_double?</h1>
  
  <p>Напомню разницу: <code>instance_double</code> может уронить тест, если застабленные методы отсутствуют в указанном классе, <code>double</code> на всё пофиг.</p>
  
  <p>По моему опыту, всегда лучше брать <code>instance_double</code>, а <code>double</code> использовать только для каких-то незначительных штук со стабильным АПИ. Например, для писем:</p>
  
  <!-- pre — preformatted text, заранее отформатированный текст. Обычно его используют для примеров кода или ASCII-арта -->
  <pre>
  <code>allow(DeadlineMailer)
    .to receive(:last_deadline_warning)
    .and_return(double(:email, deliver_later: true))
  </code>
  </pre>
</article>

Зада­дим основ­ной шрифт стра­ницы — Helvetica:

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

Зада­дим шрифт для заго­лов­ков — Georgia:

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

h1,
h2,
h3 {
  font-family: Georgia, Times, Times New Roman, serif;
}            

Зада­дим шрифт для кода:

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

h1,
h2,
h3 {
  font-family: Georgia, Times, Times New Roman, serif;
}

code {
  font-family: Consolas, Monaco, Menlo, monospace;
}

И уве­ли­чим кегль заго­лов­ков пер­вого уровня с помо­щью font-size, о кото­ром мы рас­ска­жем дальше:

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

h1,
h2,
h3 {
  font-family: Georgia, Times, Times New Roman, serif;
}

h1 {
  font-size: 2.25em;
}

code {
  font-family: Consolas, Monaco, Menlo, monospace;
}

Подберём шрифты для поста в блоге, используя веб‑безопасные Helvetica и Georgia. Начнём с разметки:

<article>
  <h1>Когда использовать double вместо instance_double?</h1>
  
  <p>Напомню разницу: <code>instance_double</code> может уронить тест, если застабленные методы отсутствуют в указанном классе, <code>double</code> на всё пофиг.</p>
  
  <p>По моему опыту, всегда лучше брать <code>instance_double</code>, а <code>double</code> использовать только для каких-то незначительных штук со стабильным АПИ. Например, для писем:</p>
  
  <!-- pre — preformatted text, заранее отформатированный текст. Обычно его используют для примеров кода или ASCII-арта -->
  <pre>
  <code>allow(DeadlineMailer)
    .to receive(:last_deadline_warning)
    .and_return(double(:email, deliver_later: true))
  </code>
  </pre>
</article>

Зададим основной шрифт страницы — Helvetica:

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

Зададим шрифт для заголовков — Georgia:

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

h1,
h2,
h3 {
  font-family: Georgia, Times, Times New Roman, serif;
}            

Зададим шрифт для кода:

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

h1,
h2,
h3 {
  font-family: Georgia, Times, Times New Roman, serif;
}

code {
  font-family: Consolas, Monaco, Menlo, monospace;
}

И увеличим кегль заголовков первого уровня с помощью font-size, о котором мы расскажем дальше:

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
}

h1,
h2,
h3 {
  font-family: Georgia, Times, Times New Roman, serif;
}

h1 {
  font-size: 2.25em;
}

code {
  font-family: Consolas, Monaco, Menlo, monospace;
}

Подключение нестандартных шрифтов. Один из самых про­стых и попу­ляр­ных сай­тов с бес­плат­ными шриф­тами — Гугль‑фонтс. Его шрифты под­клю­ча­ются к сайту за пять минут.

Выбе­рем нуж­ный нам шрифт и его начертания.

Выде­лим полу­чен­ный код и скопируем.

Вста­вим его в <head> нашей страницы:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="preconnect"
      href="https://fonts.googleapis.com">
    <link rel="preconnect"
      href="https://fonts.gstatic.com" crossorigin>
    <link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Rubik&display=swap">
  </head>
  <body>
  </body>
</html>

Выде­лим код с font-family и скопируем.

Вста­вим его в стили:

body {
  font-family: Rubik, sans-serif;
}

Советуем сразу отфильтровать шрифты, поддерживающие кириллицу

Light 300 и Regular 400 — это вес начертания, его «жирность». Italic — курсив

Гугль добавляет ссылку на стили и заклинания, чтобы шрифты грузились быстрее

Шрифты подключены, но пока не работают: браузер не знает, где их использовать

Начертаниями управляют font‑weight и font‑style, о которых мы расскажем позже

Если шрифт не загрузится, браузер возьмёт Гельветику или Ариал

Советуем сразу отфильтровать шрифты, поддерживающие кириллицу

Light 300 и Regular 400 — это вес начертания, его «жирность». Italic — курсив

Гугль добавляет ссылку на стили и заклинания, чтобы шрифты грузились быстрее

Шрифты подключены, но пока не работают: браузер не знает, где их использовать

Начертаниями управляют font‑weight и font‑style, о которых мы расскажем позже

Если шрифт не загрузится, браузер возьмёт Гельветику или Ариал

Подключение нестандартных шрифтов. Один из самых простых и популярных сайтов с бесплатными шрифтами — Гугль‑фонтс. Его шрифты подключаются к сайту за пять минут.

Выберем нужный нам шрифт и его начертания.

Выделим полученный код и скопируем.

Вставим его в <head> нашей страницы:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <link rel="preconnect"
      href="https://fonts.googleapis.com">
    <link rel="preconnect"
      href="https://fonts.gstatic.com" crossorigin>
    <link rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Rubik&display=swap">
  </head>
  <body>
  </body>
</html>

Выделим код с font-family и скопируем.

Вставим его в стили:

body {
  font-family: Rubik, sans-serif;
}

Подключение собственных шрифтов

Можно под­клю­чить к стра­нице свой файл шрифта. Кон­струк­ция @font-face объ­яв­ляет бра­у­зеру новый шрифт, его назва­ние, фор­мат и путь до файла.

Если кача­ете шрифт из интер­нета — убе­ди­тесь, что у вас есть лицен­зия для исполь­зо­ва­ния в вебе. Для неко­то­рых шриф­тов веб‑лицен­зия бесплатна.

Крайне не сове­туем исполь­зо­вать «пират­ские» шрифты и шрифты, в пра­вах исполь­зо­ва­ния кото­рых вы не уве­рены. Это может обер­нуться не только репу­та­ци­он­ными поте­рями, но и реаль­ным судеб­ным раз­би­ра­тель­ством — круп­ные шриф­то­вые ком­па­нии сегодня активно мони­то­рят веб на пред­мет непра­во­мер­ного исполь­зо­ва­ния своих шрифтов.

@font-face {
  font-family: 'My Custom Font'; /* Это название будет использоваться для применения шрифта */
  src: url('fonts/myCustomFont.woff2') format('woff2'); 
  /* Путь до файла шрифта с указанием формата — рядом с ЦСС-файлом в папке fonts */
}

/* Для начертаний конструкция дублируется, но уже с указанием другого файла и нужного font-weight, название остаётся без изменений */
@font-face {
  font-family: 'My Custom Font';
  src: url('fonts/myCustomFont-Light.woff2') format('woff2');
  font-weight: 100;
}

/* Используем объявленный шрифт на странице */
body {
  font-family: 'My Custom Font', sans-serif;
  font-weight: 100;
}

У подключения собственных шрифтов есть много нюансов, выходящих за рамки этой книги. Перед подключением ознакомьтесь с документацией: @font‑face

Подключение собственных шрифтов

Можно подключить к странице свой файл шрифта. Конструкция @font-face объявляет браузеру новый шрифт, его название, формат и путь до файла.

Если качаете шрифт из интернета — убедитесь, что у вас есть лицензия для использования в вебе. Для некоторых шрифтов веб‑лицензия бесплатна.

Крайне не советуем использовать «пиратские» шрифты и шрифты, в правах использования которых вы не уверены. Это может обернуться не только репутационными потерями, но и реальным судебным разбирательством — крупные шрифтовые компании сегодня активно мониторят веб на предмет неправомерного использования своих шрифтов.

@font-face {
  font-family: 'My Custom Font'; /* Это название будет использоваться для применения шрифта */
  src: url('fonts/myCustomFont.woff2') format('woff2'); 
  /* Путь до файла шрифта с указанием формата — рядом с ЦСС-файлом в папке fonts */
}

/* Для начертаний конструкция дублируется, но уже с указанием другого файла и нужного font-weight, название остаётся без изменений */
@font-face {
  font-family: 'My Custom Font';
  src: url('fonts/myCustomFont-Light.woff2') format('woff2');
  font-weight: 100;
}

/* Используем объявленный шрифт на странице */
body {
  font-family: 'My Custom Font', sans-serif;
  font-weight: 100;
}

У подключения собственных шрифтов есть много нюансов, выходящих за рамки этой книги. Перед подключением ознакомьтесь с документацией: @font‑face

Единицы измерения размеров

В ЦСС еди­ницы изме­ре­ния бывают отно­си­тель­ными и абсо­лют­ными. Отно­си­тель­ные счи­та­ются отно­си­тельно чего‑то ещё:

  • em — отно­си­тельно кегля родителя;

  • rem — отно­си­тельно кегля у <html>;

  • vw — отно­си­тельно ширины экрана, 1vw — 1% ширины экрана;

  • vh — отно­си­тельно высоты экрана, 1vh — 1% высоты экрана;

  • % — отно­си­тельно зна­че­ния такого же свой­ства роди­теля, но с ред­кими исключениями.

Напри­мер, width: 50% задаст ширину рав­ную поло­вине ширины роди­теля. С дру­гой сто­роны, margin-left: 25% задаст левый отступ рав­ный чет­верти от ширины роди­теля, а не от его margin-left.

Абсо­лют­ные еди­ницы изме­ре­ния при­вя­заны к кон­крет­ным раз­ме­рам: 1cm на печати — это один «физи­че­ский» сан­ти­метр в реаль­ном мире, 1px на экране — это один вир­ту­аль­ный пик­сель вашего экрана.

Боль­шая часть абсо­лют­ных еди­ниц изме­ре­ния исполь­зу­ется при выводе стра­ницы на печать: для вывода на экран сан­ти­метры и дюймы кон­вер­ти­ру­ются в пик­сели и нико­гда не сов­па­дают с реаль­ными физи­че­скими еди­ни­цами. Поэтому для ука­за­ния точ­ных и кон­крет­ных раз­ме­ров на экране вы будете исполь­зо­вать пик­сели, px.

Единицы измерения размеров

В ЦСС единицы измерения бывают относительными и абсолютными. Относительные считаются относительно чего‑то ещё:

  • em — относительно кегля родителя;

  • rem — относительно кегля у <html>;

  • vw — относительно ширины экрана, 1vw — 1% ширины экрана;

  • vh — относительно высоты экрана, 1vh — 1% высоты экрана;

  • % — относительно значения такого же свойства родителя, но с редкими исключениями.

Например, width: 50% задаст ширину равную половине ширины родителя. С другой стороны, margin-left: 25% задаст левый отступ равный четверти от ширины родителя, а не от его margin-left.

Абсолютные единицы измерения привязаны к конкретным размерам: 1cm на печати — это один «физический» сантиметр в реальном мире, 1px на экране — это один виртуальный пиксель вашего экрана.

Большая часть абсолютных единиц измерения используется при выводе страницы на печать: для вывода на экран сантиметры и дюймы конвертируются в пиксели и никогда не совпадают с реальными физическими единицами. Поэтому для указания точных и конкретных размеров на экране вы будете использовать пиксели, px.

В «рези­но­вой» вёрстке дизай­нер задаёт не только форму кон­тей­нера для тек­ста, но и пра­вила, по кото­рым она меня­ется в зави­си­мо­сти от раз­ме­ров окна и экрана.

Кегль экран­ного тек­ста обычно лежит в диа­па­зоне 12…16 пунк­тов, а интер­ли­ньяж — 1,2…1,4 от зна­че­ния кегля

Кегль и интерлиньяж

В ЦСС кегль зада­ётся свой­ством font-size, а интер­ли­ньяж — свой­ством line-height. Их зна­че­ния могут быть фик­си­ро­ван­ными — px, pt, in, cm, или отно­си­тель­ными — %, emrem:

body {
  font-size: 20px;
  /* Высота строки посчитается от кегля:
     20px * 1.25 = 25px */
  line-height: 1.25;
}

.sidenote {
  /* Кегль посчитается от кегля родителя:
     20px * 0.75 = 15px */
  font-size: .75em;
  /* Высота строки посчитается от кегля элемента:
     15px * 1.2 = 18px */
  line-height: 120%;
}

Обратите внимание: значения в рамочке в этой книге можно менять, попробуйте их подвигать

В «резиновой» вёрстке дизайнер задаёт не только форму контейнера для текста, но и правила, по которым она меняется в зависимости от размеров окна и экрана.

Кегль экранного текста обычно лежит в диапазоне 12…16 пунктов, а интерлиньяж — 1,2…1,4 от значения кегля

Кегль и интерлиньяж

В ЦСС кегль задаётся свойством font-size, а интерлиньяж — свойством line-height. Их значения могут быть фиксированными — px, pt, in, cm, или относительными — %, emrem:

body {
  font-size: 20px;
  /* Высота строки посчитается от кегля:
     20px * 1.25 = 25px */
  line-height: 1.25;
}

.sidenote {
  /* Кегль посчитается от кегля родителя:
     20px * 0.75 = 15px */
  font-size: .75em;
  /* Высота строки посчитается от кегля элемента:
     15px * 1.2 = 18px */
  line-height: 120%;
}

Обратите внимание: значения в рамочке в этой книге можно менять, попробуйте их подвигать

Обычно кегль основ­ного тек­ста задают на самом высо­ком уровне в :root или body, а для осталь­ных тек­сто­вых эле­мен­тов исполь­зуют rem или em:

/* :root — хитрый селектор, указывающий на корневой, самый высокий, элемент документа. По сути, :root идентичен <html> */
:root {
  font-size: 18px;
  /* Высота строки посчитается от кегля:
     18px * 1.25 = 22.5px */
  line-height: 1.25
}

h1 {
  /* Кегль посчитается от кегля в :root:
     18px * 2.666 = 48px */
  font-size: 2.666rem;
  /* Высота строки посчитается от кегля:
     48px * 1 = 48px */
  line-height: 1;
}

h2 {
  /* Кегль посчитается от кегля в :root:
     18px * 1.722 = 31px */
  font-size: 1.722rem;
  /* Высота строки посчитается от кегля:
     41px * 0.935 = 38px */
  line-height: .935;
}

h3 {
  font-size: 1rem;
  /* Высота строки унаследуется от :root */
  line-height: inherit;
}

Полу­ча­ется система кег­лей и интер­ли­нья­жей, зави­ся­щая от един­ствен­ного базо­вого кегля, ука­зан­ного в :root. Поме­няли кегль в :root, поме­ня­лись кегли и интер­ли­ньяжи у тек­ста и заго­лов­ков, сохра­нив те же про­пор­ции. Легко менять при пере­ходе к мобиль­ной или деск­топ­ной версии.

Текстовая страница

Приве­дём реко­мен­да­ции по вёрст­ке экран­ной тек­сто­вой стра­ни­цы со ста­тьёй, замет­кой, книж­ной гла­вой или тек­стом о компании.

В отли­чие от модуль­ной стра­ни­цы, состо­я­щей из отдель­ных бло­ков‑ато­мов, тек­сто­вая стра­ни­ца пред­став­ля­ет собой поток для после­до­ва­тель­но­го чте­ния — напри­мер как этот раз­во­рот. Подоб­но длин­ной моле­ку­ле поли­ме­ра, тек­сто­вая стра­ни­ца вклю­ча­ет в себя раз­но­об­раз­ные эле­мен­ты в сво­бод­ном поряд­ке: под­за­го­лов­ки, спис­ки, таб­ли­цы и иллюстрации.

Подзаголовки

Основ­ной текст име­ет один глав­ный заго­ло­вок пер­во­го уров­ня и дро­бит­ся на части с помо­щью подзаголовков.

Кегль заго­лов­ков вто­ро­го уров­ня обыч­но уве­ли­чен по срав­не­нию с основ­ным тек­стом. Перед таки­ми заго­лов­ка­ми про­пус­ка­ют при­мер­но две стро­ки, после дела­ют отступ, рав­ный отступу швей­цар­ского абзаца — как у над­пи­си «Подза­го­лов­ки» на этом развороте.

Заголов­ки тре­тье­го уровня

Заголов­ки тре­тье­го уров­ня удоб­но наби­рать полу­жир­ным основ­но­го кег­ля и не отби­вать от после­ду­ю­ще­го тек­ста, как над колон­ка­ми тек­ста на раз­во­роте бренд­бука Скайсканера.

Текстовая страница

Приве­дём реко­мен­да­ции по вёрст­ке экран­ной тек­сто­вой стра­ни­цы со ста­тьёй, замет­кой, книж­ной гла­вой или тек­стом о компании.

В отли­чие от модуль­ной стра­ни­цы, состо­я­щей из отдель­ных бло­ков‑ато­мов, тек­сто­вая стра­ни­ца пред­став­ля­ет собой поток для после­до­ва­тель­но­го чте­ния — напри­мер как этот раз­во­рот. Подоб­но длин­ной моле­ку­ле поли­ме­ра, тек­сто­вая стра­ни­ца вклю­ча­ет в себя раз­но­об­раз­ные эле­мен­ты в сво­бод­ном поряд­ке: под­за­го­лов­ки, спис­ки, таб­ли­цы и иллюстрации.

Подзаголовки

Основ­ной текст име­ет один глав­ный заго­ло­вок пер­во­го уров­ня и дро­бит­ся на части с помо­щью подзаголовков.

Кегль заго­лов­ков вто­ро­го уров­ня обыч­но уве­ли­чен по срав­не­нию с основ­ным тек­стом. Перед таки­ми заго­лов­ка­ми про­пус­ка­ют при­мер­но две стро­ки, после дела­ют отступ, рав­ный отступу швей­цар­ского абзаца — как у над­пи­си «Подза­го­лов­ки» на этом развороте.

Заголов­ки тре­тье­го уровня

Заголов­ки тре­тье­го уров­ня удоб­но наби­рать полу­жир­ным основ­но­го кег­ля и не отби­вать от после­ду­ю­ще­го тек­ста, как над колон­ка­ми тек­ста на раз­во­роте бренд­бука Скайсканера.

Обычно кегль основного текста задают на самом высоком уровне в :root или body, а для остальных текстовых элементов используют rem или em:

/* :root — хитрый селектор, указывающий на корневой, самый высокий, элемент документа. По сути, :root идентичен <html> */
:root {
  font-size: 18px;
  /* Высота строки посчитается от кегля:
     18px * 1.25 = 22.5px */
  line-height: 1.25
}

h1 {
  /* Кегль посчитается от кегля в :root:
     18px * 2.666 = 48px */
  font-size: 2.666rem;
  /* Высота строки посчитается от кегля:
     48px * 1 = 48px */
  line-height: 1;
}

h2 {
  /* Кегль посчитается от кегля в :root:
     18px * 1.722 = 31px */
  font-size: 1.722rem;
  /* Высота строки посчитается от кегля:
     41px * 0.935 = 38px */
  line-height: .935;
}

h3 {
  font-size: 1rem;
  /* Высота строки унаследуется от :root */
  line-height: inherit;
}

Получается система кеглей и интерлиньяжей, зависящая от единственного базового кегля, указанного в :root. Поменяли кегль в :root, поменялись кегли и интерлиньяжи у текста и заголовков, сохранив те же пропорции. Легко менять при переходе к мобильной или десктопной версии.

Лайфхак: em или rem?

И em, и rem — отно­си­тель­ные вели­чины. Раз­ница между ними в том, что em счи­та­ется отно­си­тельно кегля роди­тель­ского эле­мента, а rem — отно­си­тельно кор­не­вого. Сравните:

 20px
1.2em
20px×1.224px
0.625em 24px×0.62515px
0.8em
15px×0.812px
 20px
1.2rem
20px×1.224px
0.625rem
20px×0.62512.5px
0.8rem
20px×0.816px

Для неза­ви­си­мых ком­по­нен­тов лучше подой­дёт rem: не при­дётся бес­по­ко­иться, что роди­тель­ский эле­мент повли­яет на кегль в ком­по­ненте. Если вы, нао­бо­рот, хотите, чтобы вло­жен­ность вли­яла на кегль, берите em.

При исполь­зо­ва­нии rem кегль на кор­не­вом эле­менте (:root) лучше не зада­вать в пик­се­лях. Бра­у­зеры поз­во­ляют настро­ить базо­вый кегль тек­ста. Если задать кегль в пик­се­лях, эта настройка не сра­бо­тает, cла­бо­ви­дя­щим, воз­можно, при­дётся зумить страницу.

Вме­сто этого запом­ните, что кегль по умол­ча­нию во всех бра­у­зе­рах — 16 пик­се­лей, и зада­вайте базо­вый отно­си­тельно него. То есть, вме­сто font-size: 18px — font-size: 1.125em

Лайфхак: em или rem?

И em, и rem — относительные величины. Разница между ними в том, что em считается относительно кегля родительского элемента, а rem — относительно корневого. Сравните:

 20px
1.2em
20px×1.224px
0.625em 24px×0.62515px
0.8em
15px×0.812px
 20px
1.2rem
20px×1.224px
0.625rem
20px×0.62512.5px
0.8rem
20px×0.816px

Для независимых компонентов лучше подойдёт rem: не придётся беспокоиться, что родительский элемент повлияет на кегль в компоненте. Если вы, наоборот, хотите, чтобы вложенность влияла на кегль, берите em.

При использовании rem кегль на корневом элементе (:root) лучше не задавать в пикселях. Браузеры позволяют настроить базовый кегль текста. Если задать кегль в пикселях, эта настройка не сработает, cлабовидящим, возможно, придётся зумить страницу.

Вместо этого запомните, что кегль по умолчанию во всех браузерах — 16 пикселей, и задавайте базовый относительно него. То есть, вместо font-size: 18px — font-size: 1.125em

Вертикальный ритм

Кегль и интер­ли­ньяж задают вер­ти­каль­ный ритм в тек­сте. Также на него вли­яют отступы вокруг тек­сто­вых элементов.

Обно­вим отступы поста в блоге, исполь­зуя реко­мен­да­ции по вёрстке тек­сто­вой стра­ницы из книги «Типо­гра­фика и вёрстка».

Нач­нём с разметки:

<!-- article — стандартный тег, задающий самостоятельное содержимое страницы, которое можно использовать отдельно: новость, статью или пост в блоге -->
<article>
  <h1>Основной текст</h1>
  <p>Колонка…</p>

  <p>Текст в основной колонке…</p>

  <p>Текст, выровненный влево…</p>

  <p>Текст дробится на абзацы…</p>
</article>

Зада­дим шрифты, кегль и интер­ли­ньяж основ­ного текста:

:root {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  font-size: 1em; /* 16px по умолчанию */
  line-height: 1.3em;
}

h1,
h2 {
  font-family: Georgia, Times, Times New Roman, serif;
}

Сбро­сим отступы по умол­ча­нию и огра­ни­чим ширину тек­сто­вой колонки:

:root {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  font-size: 1em;
  line-height: 1.3em;
}

h1,
h2 {
  font-family: Georgia, Times, Times New Roman, serif;
}

h1,
h2,
h3,
p {
  margin: 0;
}

article {
  max-width: 70%;
}

Настроим межа­б­зац­ный отступ:

article {
  max-width: 70%;
}

/* Селектор * + p выделяет абзацы, идущие после любых элементов */
* + p {
  margin-top: .825rem;
}

И настроим заго­ловки пер­вого уровня:

article {
  max-width: 70%;
}

h1 {
  font-size: 2.25rem;
  line-height: 1.3em;
  font-weight: bold;
}

h1 + p {
  margin-top: 3rem;
}

* + p {
  margin-top: .825rem;
}

Вертикальный ритм

Кегль и интерлиньяж задают вертикальный ритм в тексте. Также на него влияют отступы вокруг текстовых элементов.

Обновим отступы поста в блоге, используя рекомендации по вёрстке текстовой страницы из книги «Типографика и вёрстка».

Начнём с разметки:

<!-- article — стандартный тег, задающий самостоятельное содержимое страницы, которое можно использовать отдельно: новость, статью или пост в блоге -->
<article>
  <h1>Основной текст</h1>
  <p>Колонка…</p>

  <p>Текст в основной колонке…</p>

  <p>Текст, выровненный влево…</p>

  <p>Текст дробится на абзацы…</p>
</article>

Зададим шрифты, кегль и интерлиньяж основного текста:

:root {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  font-size: 1em; /* 16px по умолчанию */
  line-height: 1.3em;
}

h1,
h2 {
  font-family: Georgia, Times, Times New Roman, serif;
}

Сбросим отступы по умолчанию и ограничим ширину текстовой колонки:

:root {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  font-size: 1em;
  line-height: 1.3em;
}

h1,
h2 {
  font-family: Georgia, Times, Times New Roman, serif;
}

h1,
h2,
h3,
p {
  margin: 0;
}

article {
  max-width: 70%;
}

Настроим межабзацный отступ:

article {
  max-width: 70%;
}

/* Селектор * + p выделяет абзацы, идущие после любых элементов */
* + p {
  margin-top: .825rem;
}

И настроим заголовки первого уровня:

article {
  max-width: 70%;
}

h1 {
  font-size: 2.25rem;
  line-height: 1.3em;
  font-weight: bold;
}

h1 + p {
  margin-top: 3rem;
}

* + p {
  margin-top: .825rem;
}

Настроим отступы у под­за­го­лов­ков. Доба­вим их в разметку:

<article>
  …
  <p>Основной текст…</p>

  <h2>Подзаголовки</h2>
  <p>Кегль заголовков второго уровня…</p>

  <h3>Заголовки третьего уровня</h3>
  <p>Заголовки третьего уровня…</p>
  <p>Подзаголовки в иерархии…</p>
</article>

Настроим заго­ловки вто­рого уровня:

h1 + p {
  margin-top: 3rem;
}

h2 {
  font-size: 2rem;
  line-height: 1.3em;
  /* Сделаем подзаголовки нежирными */
  font-weight: normal;
}

* + h2 {
  margin-top: 1.25rem;
}

h2 + p {
  margin-top: .5rem;
}

* + p {
  margin-top: .825rem;
}

И обно­вим заго­ловки тре­тьего уровня:

h2 + p {
  margin-top: .5rem;
}

h3 {
  font-size: 1rem;
  line-height: 1.3em;
  font-weight: bold;
}

* + h3 {
  margin-top: 1.75rem;
}

h3 + p {
  margin-top: 0;
}

* + p {
  margin-top: .825rem;
}

Настроим отступы у подзаголовков. Добавим их в разметку:

<article>
  …
  <p>Основной текст…</p>

  <h2>Подзаголовки</h2>
  <p>Кегль заголовков второго уровня…</p>

  <h3>Заголовки третьего уровня</h3>
  <p>Заголовки третьего уровня…</p>
  <p>Подзаголовки в иерархии…</p>
</article>

Настроим заголовки второго уровня:

h1 + p {
  margin-top: 3rem;
}

h2 {
  font-size: 2rem;
  line-height: 1.3em;
  /* Сделаем подзаголовки нежирными */
  font-weight: normal;
}

* + h2 {
  margin-top: 1.25rem;
}

h2 + p {
  margin-top: .5rem;
}

* + p {
  margin-top: .825rem;
}

И обновим заголовки третьего уровня:

h2 + p {
  margin-top: .5rem;
}

h3 {
  font-size: 1rem;
  line-height: 1.3em;
  font-weight: bold;
}

* + h3 {
  margin-top: 1.75rem;
}

h3 + p {
  margin-top: 0;
}

* + p {
  margin-top: .825rem;
}

Настроим отступы у спис­ков. Нач­нём с разметки:

<article>
  …
  <h2>Списки</h2>
  <p>Список, или перечень…</p>

  <p>Первый элемент перечня …</p>

  <p>Элементы списков обозначаются:</p>
  <ul>
    <li>арабскими цифрами с точкой;</li>
    <li>арабскими цифрами или русскими строчными буквами;</li>
    <li>с закрывающей скобкой;</li>
    <li>графическими маркерами или наборными знаками;</li>
    <li>втяжкой.</li>
  </ul>

  <p>Если для чистоты вёрстки…</p>
</article>

Зада­дим отступы списка и его элементов:

h1,
h2,
h3,
p,
ul, /* Ненумерованный список, unordered list */
ol, /* Нумерованный список, ordered list */
li { /* Элемент списка, list item */
  margin: 0;
}

* + li {
  margin-top: .4rem;
}

И настроим внеш­ний вид списка:

* + li {
  margin-top: .4rem;
}

ul {
  list-style: none outside; /* Прячем буллиты */
  margin-left: 2rem;               /* Добавляем втяжку */
  padding-left: 0;
}

Настроим отступы у списков. Начнём с разметки:

<article>
  …
  <h2>Списки</h2>
  <p>Список, или перечень…</p>

  <p>Первый элемент перечня …</p>

  <p>Элементы списков обозначаются:</p>
  <ul>
    <li>арабскими цифрами с точкой;</li>
    <li>арабскими цифрами или русскими строчными буквами;</li>
    <li>с закрывающей скобкой;</li>
    <li>графическими маркерами или наборными знаками;</li>
    <li>втяжкой.</li>
  </ul>

  <p>Если для чистоты вёрстки…</p>
</article>

Зададим отступы списка и его элементов:

h1,
h2,
h3,
p,
ul, /* Ненумерованный список, unordered list */
ol, /* Нумерованный список, ordered list */
li { /* Элемент списка, list item */
  margin: 0;
}

* + li {
  margin-top: .4rem;
}

И настроим внешний вид списка:

* + li {
  margin-top: .4rem;
}

ul {
  list-style: none outside; /* Прячем буллиты */
  margin-left: 2rem;               /* Добавляем втяжку */
  padding-left: 0;
}

Вот что, в итоге, у нас получилось:

index.html

<article>
  <h1>Основной текст</h1>
  <p>Колонка…</p>

  <p>Текст в основной колонке…</p>

  <p>Текст, выровненный влево…</p>

  <p>Текст дробится на абзацы…</p>
  
  <h2>Подзаголовки</h2>
  <p>Кегль заголовков второго уровня…</p>

  <h3>Заголовки третьего уровня</h3>
  <p>Заголовки третьего уровня…</p>
  <p>Подзаголовки в иерархии…</p>
  
  <h2>Списки</h2>
  <p>Список, или перечень…</p>

  <p>Первый элемент перечня …</p>

  <p>Элементы списков обозначаются:</p>
  <ul>
    <li>арабскими цифрами с точкой;</li>
    <li>арабскими цифрами или русскими строчными буквами;</li>
    <li>с закрывающей скобкой;</li>
    <li>графическими маркерами или наборными знаками;</li>
    <li>втяжкой.</li>
  </ul>

  <p>Если для чистоты вёрстки…</p>
</article>

style.css

:root {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  font-size: 1em;
  line-height: 1.3em;
}

h1,
h2 {
  font-family: Georgia, Times, Times New Roman, serif;
}

h1,
h2,
h3,
p,
ul,
ol,
li {
  margin: 0;
}

article {
  max-width: 70%;
}

h1 {
  font-size: 2.25rem;
  line-height: 1.3em;
  font-weight: bold;
}

h1 + p {
  margin-top: 3rem;
}

h2 {
  font-size: 2rem;
  line-height: 1.3em;
  font-weight: normal;
}

* + h2 {
  margin-top: 1.25rem;
}

h2 + p {
  margin-top: .5rem;
}

h3 {
  font-size: 1rem;
  line-height: 1.3em;
  font-weight: bold;
}

* + h3 {
  margin-top: 1.75rem;
}

h3 + p {
  margin-top: 0;
}

ul {
  list-style: none outside;
  margin-left: 2rem;
  padding-left: 0;
}

* + p {
  margin-top: .825rem;
}

* + li {
  margin-top: .4rem;
}

Основной текст

Колонка основного текста занимает 60⁠—70% ширины окна компьютера или планшета. В «резиновой» вёрстке ширина текстовой полосы уменьшается или увеличивается с изменением размера окна или экрана. Но разрешение экранов постоянно растёт, поэтому имеет смысл ограничивать максимальную ширину колонки текста, чтобы строки не становились чрезмерно длинными, а страница — трудной для чтения.

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

Текст, выровненный влево с рваным правым краем, должен «дышать» справа, то есть край формата или соседняя колонка не должны подходить к нему слишком близко.

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

Подзаголовки

Кегль заголовков второго уровня обычно увеличен по сравнению с основным текстом. Перед такими заголовками пропускают примерно две строки, после делают отступ, равный отступу швейцарского абзаца — как у надписи «Подзаголовки» на этом развороте.

Заголовки третьего уровня

Заголовки третьего уровня удобно набирать полужирным основного кегля и не отбивать от последующего текста, как над колонками текста на развороте брендбука Скайсканера.

Подзаголовки в иерархии могут быть оформлены комбинацией разных стилей: кегля, полужирного начертания, капители, курсива и выделения цветом. Главные правила — чередование, то есть яркий контраст стиля заголовков на соседних уровнях вложенности, и чувство меры.

Списки

Список, или перечень, — это предложение с однородными членами, разбитыми для удобства чтения на отдельные строки, обозначенными графическими маркерами или наборными символами. Поэтому любой список предваряется обобщающим текстом (общей, неповторяющейся частью предложения) или заголовком в той же синтаксической роли.

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

Элементы списков обозначаются:

  • арабскими цифрами с точкой;

  • арабскими цифрами или русскими строчными буквами;

  • с закрывающей скобкой;

  • графическими маркерами или наборными знаками;

  • втяжкой.

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

Вот что, в итоге, у нас получилось:

index.html

<article>
  <h1>Основной текст</h1>
  <p>Колонка…</p>

  <p>Текст в основной колонке…</p>

  <p>Текст, выровненный влево…</p>

  <p>Текст дробится на абзацы…</p>
  
  <h2>Подзаголовки</h2>
  <p>Кегль заголовков второго уровня…</p>

  <h3>Заголовки третьего уровня</h3>
  <p>Заголовки третьего уровня…</p>
  <p>Подзаголовки в иерархии…</p>
  
  <h2>Списки</h2>
  <p>Список, или перечень…</p>

  <p>Первый элемент перечня …</p>

  <p>Элементы списков обозначаются:</p>
  <ul>
    <li>арабскими цифрами с точкой;</li>
    <li>арабскими цифрами или русскими строчными буквами;</li>
    <li>с закрывающей скобкой;</li>
    <li>графическими маркерами или наборными знаками;</li>
    <li>втяжкой.</li>
  </ul>

  <p>Если для чистоты вёрстки…</p>
</article>

style.css

:root {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  font-size: 1em;
  line-height: 1.3em;
}

h1,
h2 {
  font-family: Georgia, Times, Times New Roman, serif;
}

h1,
h2,
h3,
p,
ul,
ol,
li {
  margin: 0;
}

article {
  max-width: 70%;
}

h1 {
  font-size: 2.25rem;
  line-height: 1.3em;
  font-weight: bold;
}

h1 + p {
  margin-top: 3rem;
}

h2 {
  font-size: 2rem;
  line-height: 1.3em;
  font-weight: normal;
}

* + h2 {
  margin-top: 1.25rem;
}

h2 + p {
  margin-top: .5rem;
}

h3 {
  font-size: 1rem;
  line-height: 1.3em;
  font-weight: bold;
}

* + h3 {
  margin-top: 1.75rem;
}

h3 + p {
  margin-top: 0;
}

ul {
  list-style: none outside;
  margin-left: 2rem;
  padding-left: 0;
}

* + p {
  margin-top: .825rem;
}

* + li {
  margin-top: .4rem;
}

Основной текст

Колонка основного текста занимает 60⁠—70% ширины окна компьютера или планшета. В «резиновой» вёрстке ширина текстовой полосы уменьшается или увеличивается с изменением размера окна или экрана. Но разрешение экранов постоянно растёт, поэтому имеет смысл ограничивать максимальную ширину колонки текста, чтобы строки не становились чрезмерно длинными, а страница — трудной для чтения.

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

Текст, выровненный влево с рваным правым краем, должен «дышать» справа, то есть край формата или соседняя колонка не должны подходить к нему слишком близко.

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

Подзаголовки

Кегль заголовков второго уровня обычно увеличен по сравнению с основным текстом. Перед такими заголовками пропускают примерно две строки, после делают отступ, равный отступу швейцарского абзаца — как у надписи «Подзаголовки» на этом развороте.

Заголовки третьего уровня

Заголовки третьего уровня удобно набирать полужирным основного кегля и не отбивать от последующего текста, как над колонками текста на развороте брендбука Скайсканера.

Подзаголовки в иерархии могут быть оформлены комбинацией разных стилей: кегля, полужирного начертания, капители, курсива и выделения цветом. Главные правила — чередование, то есть яркий контраст стиля заголовков на соседних уровнях вложенности, и чувство меры.

Списки

Список, или перечень, — это предложение с однородными членами, разбитыми для удобства чтения на отдельные строки, обозначенными графическими маркерами или наборными символами. Поэтому любой список предваряется обобщающим текстом (общей, неповторяющейся частью предложения) или заголовком в той же синтаксической роли.

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

Элементы списков обозначаются:

  • арабскими цифрами с точкой;

  • арабскими цифрами или русскими строчными буквами;

  • с закрывающей скобкой;

  • графическими маркерами или наборными знаками;

  • втяжкой.

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

Выключка

Выключка зада­ётся свой­ством text-align с клю­че­выми сло­вами left (левый флаг), center (по цен­тру), right (пра­вый флаг) и justify (по формату).

text‑align: left

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

В отличие от заголовка, который стремится по форме к линии, текст — это прямоугольник. По пропорциям прямоугольник может быть широким — горизонтальным, высоким — вертикальным или квадратным.

text‑align: right

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

В отличие от заголовка, который стремится по форме к линии, текст — это прямоугольник. По пропорциям прямоугольник может быть широким — горизонтальным, высоким — вертикальным или квадратным.

text‑align: center

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

В отличие от заголовка, который стремится по форме к линии, текст — это прямоугольник. По пропорциям прямоугольник может быть широким — горизонтальным, высоким — вертикальным или квадратным.

text‑align: justify

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

В отличие от заголовка, который стремится по форме к линии, текст — это прямоугольник. По пропорциям прямоугольник может быть широким — горизонтальным, высоким — вертикальным или квадратным.

Выключка

Выключка задаётся свойством text-align с ключевыми словами left (левый флаг), center (по центру), right (правый флаг) и justify (по формату).

text‑align: left

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

text‑align: right

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

text‑align: center

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

text‑align: justify

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

Text is a basic element of layout

In “responsive” layout of a web page, designer specifies not only the shape of the text container but also the rules by which that shape changes depending on window and screen size.

Type size in CSS

In CSS type size of the screen text usually lies in the range of 12 to 16 points and line spacing is 120–140 % of the type size.

Регистр

Регистр зада­ётся свой­ством text-transform с клю­че­выми сло­вами uppercase (все про­пис­ные), lowercase (все строч­ные) и capitalize (пер­вые бук­вые каж­дого слова прописными):

h1 {
  text-transform: capitalize; /* «Капитализируем» заголовок статьи */
}

h2 {
  /* Подзаголовки набираем заглавными
     и жирным курсивом */
  text-transform: uppercase;
  font-style: italic;
  font-weight: bold;
}

abbr {
  font-variant: all-small-caps; /* Аббревиатуры набираем капителью */
}

Text is a basic element of layout

In “responsive” layout of a web page, designer specifies not only the shape of the text container but also the rules by which that shape changes depending on window and screen size.

Type size in CSS

In CSS type size of the screen text usually lies in the range of 12 to 16 points and line spacing is 120–140 % of the type size.

Регистр

Регистр задаётся свойством text-transform с ключевыми словами uppercase (все прописные), lowercase (все строчные) и capitalize (первые буквые каждого слова прописными):

h1 {
  text-transform: capitalize; /* «Капитализируем» заголовок статьи */
}

h2 {
  /* Подзаголовки набираем заглавными
     и жирным курсивом */
  text-transform: uppercase;
  font-style: italic;
  font-weight: bold;
}

abbr {
  font-variant: all-small-caps; /* Аббревиатуры набираем капителью */
}

Капитель, лигатуры и фичи шрифта

Капи­тель и лига­туры — фичи шрифта: какие‑то шрифты их под­дер­жи­вают, какие‑то нет. Вклю­ча­ются через font-variant:

.smallcapitals {
  font-variant: all-small-caps; /* В отличие от small-caps набирает капителью и строчные, и прописные */
}

.liga {
  font-variant: common-ligatures; /* Включаем лигатуры */
}

Для тон­кой настройки исполь­зу­е­мых фич шрифта исполь­зуют font-feature-settings:

body {
  /* Используем кернинг и дроби:
     1/3 превратится в ⅓ */
  font-feature-settings: 'kern', 'frac';
}

Без капители и лигатур

КПСС, ЦСС и difficulty

С капителью и лигатурами

КПСС, ЦСС и difficulty

Без кернинга и дробей

1/3 часть суши
1990—2017

С кернингом и дробями

1/3 часть суши
1990—2017

Капитель, лигатуры и фичи шрифта

Капитель и лигатуры — фичи шрифта: какие‑то шрифты их поддерживают, какие‑то нет. Включаются через font-variant:

.smallcapitals {
  font-variant: all-small-caps; /* В отличие от small-caps набирает капителью и строчные, и прописные */
}

.liga {
  font-variant: common-ligatures; /* Включаем лигатуры */
}

Для тонкой настройки используемых фич шрифта используют font-feature-settings:

body {
  /* Используем кернинг и дроби:
     1/3 превратится в ⅓ */
  font-feature-settings: 'kern', 'frac';
}

Без капители и лигатур

КПСС, ЦСС и difficulty

С капителью и лигатурами

КПСС, ЦСС и difficulty

Без кернинга и дробей

1/3 часть суши
1990—2017

С кернингом и дробями

1/3 часть суши
1990—2017

Межбуквенный и межсловный пробел

Чтобы изме­нить меж­бук­вен­ное или меж­с­лов­ное рас­сто­я­ние, исполь­зуют letter-spacing и word-spacing. Рас­сто­я­ние можно ука­зы­вать как в пик­се­лях, так и в отно­си­тель­ных еди­ни­цах, напри­мер, в em или rem.

Умень­шим меж­бук­вен­ное рас­сто­я­ние в «Метро» на 5,5 пикселей:

.metro,
.metropoliten,
.moscow-metro {
  text-transform: uppercase;
  text-align: center;
}

.metro {
  letter-spacing: -5.5px;
}

Раз­ря­дим буквы в «Мет­ро­по­ли­тен» на .25em:

.metropoliten {
  letter-spacing: .25em;
}

Уве­ли­чим рас­сто­я­ние между сло­вами на .2em:

.moscow-metro {
  word-spacing: .2em;
}

Интересный момент: и letter‑spacing, и word‑spacing задают не ширину пробела, а приращение к его ширине по умолчанию в шрифте. Соответственно, letter‑spacing: 0 не схлопнет растояние между буквами, а сделает его стандартным

Метро

Метрополитен

Мос­ков­ский Мет­ро­по­ли­тен
имени Вла­ди­мира Ильича Ленина

Метро

Метрополитен

Московский Метрополитен
имени Владимира Ильича Ленина

Межбуквенный и межсловный пробел

Чтобы изменить межбуквенное или межсловное расстояние, используют letter-spacing и word-spacing. Расстояние можно указывать как в пикселях, так и в относительных единицах, например, в em или rem.

Уменьшим межбуквенное расстояние в «Метро» на 5,5 пикселей:

.metro,
.metropoliten,
.moscow-metro {
  text-transform: uppercase;
  text-align: center;
}

.metro {
  letter-spacing: -5.5px;
}

Разрядим буквы в «Метрополитен» на .25em:

.metropoliten {
  letter-spacing: .25em;
}

Увеличим расстояние между словами на .2em:

.moscow-metro {
  word-spacing: .2em;
}

Интересный момент: и letter‑spacing, и word‑spacing задают не ширину пробела, а приращение к его ширине по умолчанию в шрифте. Соответственно, letter‑spacing: 0 не схлопнет растояние между буквами, а сделает его стандартным

normalitalic
BureausansBureausans
BureauserifBureauserif
HelveticaHelvetica
GeorgiaGeorgia
VerdanaVerdana
TrebuchetTrebuchet
PalatinoPalatino

Начертания

В ЦСС начер­та­нием управ­ляют свой­ства font-style, отве­ча­ю­щий за кур­сив и наклон, и font-weight, отве­ча­ю­щий за «жир­ность» текста.

font-style зада­ётся клю­че­выми сло­вами: normal, italic и oblique. Чаще всего исполь­зуют normal и italic, чтобы, соот­вет­ственно, убрать или доба­вить курсив:

font‑style: normal

font‑style: italic

Кур­сив и наклон­ное начер­та­ние — не одно и то же. Кур­сив — начер­та­ние, ими­ти­ру­ю­щее руко­пис­ный текст, а наклон­ное начер­та­ние обра­зу­ется путем наклона обыч­ных зна­ков вправо.

Чтобы исполь­зо­вать начер­та­ние, шрифт дол­жен его под­дер­жи­вать. Если шрифт не под­дер­жи­вает наклон­ное начер­та­ние, бра­у­зер возь­мёт вме­сто него кур­сив­ное. Если шрифт не под­дер­жи­вает и кур­сив­ное начер­та­ние, бра­у­зер меха­ни­че­ски накло­нит буквы, симу­ли­руя начертание.

Начертания

В ЦСС начертанием управляют свойства font-style, отвечающий за курсив и наклон, и font-weight, отвечающий за «жирность» текста.

font-style задаётся ключевыми словами: normal, italic и oblique. Чаще всего используют normal и italic, чтобы, соответственно, убрать или добавить курсив:

font‑style: normal

font‑style: italic

Курсив и наклонное начертание — не одно и то же. Курсив — начертание, имитирующее рукописный текст, а наклонное начертание образуется путем наклона обычных знаков вправо.

Чтобы использовать начертание, шрифт должен его поддерживать. Если шрифт не поддерживает наклонное начертание, браузер возьмёт вместо него курсивное. Если шрифт не поддерживает и курсивное начертание, браузер механически наклонит буквы, симулируя начертание.

Bureausans
Bureausans
Bureauserif
Bureauserif
Helvetica
Helvetica
Georgia
Georgia
Verdana
Verdana
Trebuchet
Trebuchet
Palatino
Palatino

font-weight отве­чает за «жир­ность», вес начер­та­ния. Зада­ется в чис­лах от 100 до 900.

Кроме чис­ло­вых зна­че­ний под­дер­жи­ва­ются клю­че­вые слова: normal, bold, lighter и bolder.

normal — это обыч­ное начер­та­ние, сино­ним font-weight: 400

font‑weight: 400
font‑weight: normal

bold — жир­ное начер­та­ние, сино­ним font-weight: 700

font‑weight: 700
font‑weight: bold

Лучше исполь­зо­вать клю­че­вые слова, а не их чис­ло­вые зна­че­ния: их проще вос­при­ни­мать, не нужно запо­ми­нать маги­че­ские числа.

Жир­ность начер­та­ния отно­си­тельно роди­теля зада­ётся с помо­щью клю­че­вых слов lighter (полегче) и bolder (пожирнее):

font‑weight: lighter
font‑weight: bolder

lighterfont‑weightbolder
light100, lightregular
light200, lightregular
light300, lightregular
light400, regularbold
light500, regularbold
regular600, boldbold
regular700, boldbold
bold800, boldbold
bold900, boldbold

Как меняется начертание от lighter, bolder и font‑weight родителя

font-weight отвечает за «жирность», вес начертания. Задается в числах от 100 до 900.

Кроме числовых значений поддерживаются ключевые слова: normal, bold, lighter и bolder.

normal — это обычное начертание, синоним font-weight: 400

font‑weight: 400
font‑weight: normal

bold — жирное начертание, синоним font-weight: 700

font‑weight: 700
font‑weight: bold

Лучше использовать ключевые слова, а не их числовые значения: их проще воспринимать, не нужно запоминать магические числа.

Жирность начертания относительно родителя задаётся с помощью ключевых слов lighter (полегче) и bolder (пожирнее):

font‑weight: lighter
font‑weight: bolder

light
font‑weight100, light
bolderregular
light
font‑weight200, light
bolderregular
light
font‑weight300, light
bolderregular
light
font‑weight400, regular
bolderbold
light
font‑weight500, regular
bolderbold
regular
font‑weight600, bold
bolderbold
regular
font‑weight700, bold
bolderbold
bold
font‑weight800, bold
bolderbold
bold
font‑weight900, bold
bolderbold

Как меняется начертание от lighter, bolder и font‑weight родителя

Бюро­санс
Бюросайн

Бюросанс поддерживает тонкое, нормальное и жирное начертания. Бюросайн — вариативный шрифт, поддерживающий все начертания, включая промежуточные

Чтобы исполь­зо­вать какое‑то начер­та­ние, шрифт дол­жен его под­дер­жи­вать. В про­тив­ном слу­чае бра­у­зер под­бе­рёт бли­жай­шее под­хо­дя­щее начер­та­ние из тех, что доступны ему в шрифте.

Отдель­ная исто­рия — вари­а­тив­ные шрифты, в кото­рых есть любые начер­та­ния. Они рабо­тают по прин­ципу интер­по­ля­ции: с помо­щью мате­ма­тики вычис­ляют неиз­вест­ные про­ме­жу­точ­ные начер­та­ния, исполь­зуя извест­ные крайние.

Как и в обыч­ных шриф­тах для зада­ния «жир­но­сти» в вари­а­тив­ных шриф­тах нужно исполь­зо­вать font‑weight:
font-weight: 400

Бюросанс
Бюросайн

Чтобы использовать какое‑то начертание, шрифт должен его поддерживать. В противном случае браузер подберёт ближайшее подходящее начертание из тех, что доступны ему в шрифте.

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

Как и в обычных шрифтах для задания «жирности» в вариативных шрифтах нужно использовать font‑weight:
font-weight: 400

Бюросанс поддерживает тонкое, нормальное и жирное начертания. Бюросайн — вариативный шрифт, поддерживающий все начертания, включая промежуточные

Стили оформления абзацев

На сегодняшний день существуют два приемлемых стиля оформления абзацев на бумаге. Классический, с абзацным отступом, подходящий для книг и спокойного чтения, хорошо сочетающийся с полной выключкой.

И «швейцарский», со слепой строкой, подходящий для повседневных современных документов, прекрасно живущий в ортогональной многоколоночной сетке и флаговом наборе.

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

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

Красная строка

В ЦСС отступ крас­ной строки, втяжка абзаца, зада­ётся свой­ством text-indent. Его зна­че­ния могут быть фик­си­ро­ван­ными, напри­мер, в пик­се­лях или отно­си­тель­ными, напри­мер, в про­цен­тах или em:

p {
  text-indent: 1em; /* Задаём абзацный отступ в круглую */
  margin: 0; /* И убираем вертикальные отступы */
}

p:first-child,
h1 + p,
h2 + p,
h3 + p {
  /* У абзацев, стоящих первыми
     или сразу после заголовков
     и подзаголовков,
     абзацный отступ избыточен:
     можно его обнулить */
  text-indent: 0;
}

Стили оформления абзацев

На сегодняшний день существуют два приемлемых стиля оформления абзацев на бумаге. Классический, с абзацным отступом, подходящий для книг и спокойного чтения, хорошо сочетающийся с полной выключкой.

И «швейцарский», со слепой строкой, подходящий для повседневных современных документов, прекрасно живущий в ортогональной многоколоночной сетке и флаговом наборе.

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

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

Красная строка

В ЦСС отступ красной строки, втяжка абзаца, задаётся свойством text-indent. Его значения могут быть фиксированными, например, в пикселях или относительными, например, в процентах или em:

p {
  text-indent: 1em; /* Задаём абзацный отступ в круглую */
  margin: 0; /* И убираем вертикальные отступы */
}

p:first-child,
h1 + p,
h2 + p,
h3 + p {
  /* У абзацев, стоящих первыми
     или сразу после заголовков
     и подзаголовков,
     абзацный отступ избыточен:
     можно его обнулить */
  text-indent: 0;
}

Упражнение: кегль, шрифты, выключка и регистр

Изме­ните ЦСС так, чтобы пер­вая тек­сто­вая стра­ница повто­рила вторую.


  
font-family: Bureauserif, serif;
font-family: Roboto Condensed, sans-serif;
font-size: 1.125em;
font-size: 3em;
font-style: italic;
font-variant: all-small-caps;
font-weight: bold;
line-height: 1.25;
line-height: .9;
margin-top: 2em;
margin: 0;
margin: 0;
text-align: right;
text-indent: 0;
text-indent: 1em;
text-transform: uppercase;
.exerciseFontBasics h1 {
}
.exerciseFontBasics p {
}
.exerciseFontBasics h1 + p {
}
.exerciseFontBasics .smallcapitals {
}

Вёрстка

Скоро
скидки

Текст — базовый элемент вёрстки.

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

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

Дизайн

Скоро
скидки

Текст — базовый элемент вёрстки.

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

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

Вёрстка

Скоро
скидки

Текст — базовый элемент вёрстки.

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

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

Дизайн

Скоро
скидки

Текст — базовый элемент вёрстки.

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

Иногда дизайнер влияет на объём текста, задавая редакторам ограничения длины подзаголовков, цитат, въездов и других особых элементов. Но чаще текст — это данность, а верстальщик работает с его формой.

Упражнение: кегль, шрифты, выключка и регистр

Измените ЦСС так, чтобы первая текстовая страница повторила вторую.


  
font-family: Bureauserif, serif;
font-family: Roboto Condensed, sans-serif;
font-size: 1.125em;
font-size: 3em;
font-style: italic;
font-variant: all-small-caps;
font-weight: bold;
line-height: 1.25;
line-height: .9;
margin-top: 2em;
margin: 0;
margin: 0;
text-align: right;
text-indent: 0;
text-indent: 1em;
text-transform: uppercase;
.exerciseFontBasics h1 {
}
.exerciseFontBasics p {
}
.exerciseFontBasics h1 + p {
}
.exerciseFontBasics .smallcapitals {
}

Цвет

Бордовый алерт с полупрозрачным фоном

Цвет тек­ста и фона задают свой­ствами color и background-color. Можно исполь­зо­вать HSL, RGB или RGBA, если нужно доба­вить цвету прозрачность:

.alert {
  /* Задаём бордовый цвет текста в HSL */
  color: hsl(354.6, 61%, 32.2%);

  /* В RGB */
  color: rgb(132, 32, 41);

  /* Он же в шестнадцатеричном виде */
  color: #842029;

  /* Для фона берём красный
     с 15% прозрачностью */
  background-color: rgba(255, 0, 0, .15);

  /* И добавляем внутренние отступы
     для аккуратности */
  padding: 21px 11px 40px;
}

Инвертированная плашка с рамкой в 2 пк

Когда точ­ные цвета неважны, напри­мер, во время отладки или в демон­стра­ци­он­ных целях, быст­рее исполь­зо­вать име­но­ван­ные цвета:

.inverted {
  color: white;
  background-color: black;
  border: 2px solid red;
  padding: 19px 9px 38px;
}

Цвет

Бордовый алерт с полупрозрачным фоном

Цвет текста и фона задают свойствами color и background-color. Можно использовать HSL, RGB или RGBA, если нужно добавить цвету прозрачность:

.alert {
  /* Задаём бордовый цвет текста в HSL */
  color: hsl(354.6, 61%, 32.2%);

  /* В RGB */
  color: rgb(132, 32, 41);

  /* Он же в шестнадцатеричном виде */
  color: #842029;

  /* Для фона берём красный
     с 15% прозрачностью */
  background-color: rgba(255, 0, 0, .15);

  /* И добавляем внутренние отступы
     для аккуратности */
  padding: 21px 11px 40px;
}

Инвертированная плашка с рамкой в 2 пк

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

.inverted {
  color: white;
  background-color: black;
  border: 2px solid red;
  padding: 19px 9px 38px;
}
h1 + p:first-letter {
  float: left; /* Настраиваем буквицу: задаём обтекание текстом */
  font-size: 2.7em; /* Увеличиваем кегль */
  line-height: .25em; /* Уменьшаем интерлиньяж, чтобы он не взрывал интерлиньяж абзаца */
  margin-top: .37em; /* Двигаем ближе к тексту, чтобы занять ровно 2 строки */
}

::selection {
  background: #02aff0; /* Меняем цвет и фон выделенного текста */
  color: #fff;
}

code ::selection {
  background: #000;
  color: #31ff53;
  text-shadow: -1px 1px #0043ff;
}

Sometimes, layout designer controls the volume of text by limiting the length of subheads, quotes, sidebars, and other special elements. But often text is given as is, and layout designer works only with its shape.

Буквица, первая строка и выделение

Для управ­ле­ния пер­вой бук­вой, пер­вой стро­кой и выде­ле­нием тек­ста исполь­зуют псев­до­эле­менты — «вир­ту­аль­ные» кусочки тек­ста, кото­рые можно исполь­зо­вать в ЦСС, не добав­ляя лиш­нюю раз­метку. Пер­вой букве тек­ста соот­вет­ствует псев­до­эле­мент :first-letter, пер­вой строке — :first-line, выде­ле­нию — ::selection

Сти­ли­за­ция выде­ле­ния не рабо­тает на тач‑устройствах.

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

Например, в ::selection можно менять цвет, фон, тень и подчёркивание‑зачёркивание текста, но изменить шрифт, кегль или интерлиньяж не получится.

Весь этот разворот — рабочий пример. Попробуйте выделить текст или код

Sometimes, layout designer controls the volume of text by limiting the length of subheads, quotes, sidebars, and other special elements. But often text is given as is, and layout designer works only with its shape.

Буквица, первая строка и выделение

Для управления первой буквой, первой строкой и выделением текста используют псевдоэлементы — «виртуальные» кусочки текста, которые можно использовать в ЦСС, не добавляя лишнюю разметку. Первой букве текста соответствует псевдоэлемент :first-letter, первой строке — :first-line, выделению — ::selection

Стилизация выделения не работает на тач‑устройствах.

h1 + p:first-letter {
  float: left; /* Настраиваем буквицу: задаём обтекание текстом */
  font-size: 2.7em; /* Увеличиваем кегль */
  line-height: .25em; /* Уменьшаем интерлиньяж, чтобы он не взрывал интерлиньяж абзаца */
  margin-top: .37em; /* Двигаем ближе к тексту, чтобы занять ровно 2 строки */
}

::selection {
  background: #02aff0; /* Меняем цвет и фон выделенного текста */
  color: #fff;
}

code ::selection {
  background: #000;
  color: #31ff53;
  text-shadow: -1px 1px #0043ff;
}

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

Например, в ::selection можно менять цвет, фон, тень и подчёркивание‑зачёркивание текста, но изменить шрифт, кегль или интерлиньяж не получится.

Подчёркивание, зачёркивание и надчёркивание

Допол­ни­тельно офор­мить текст под­чёр­ки­ва­нием, зачёр­ки­ва­нием или над­чёр­ки­ва­нием можно с помо­щью text-decoration или border-bottom. Кроме того, в Хроме можно задать вид, стиль и цвет исполь­зу­е­мой линии:

/* Убираем подчёркивание у ссылок в шапке */
header a {
  text-decoration: none;
}

/* Подчёркиваем важные моменты (em от emphasis) в статье.
   Старые версии браузеров покажут чёрное подчёркивание */
em {
  text-decoration: underline;
  
  /* Новые браузеры покажут жёлтое подчёркивание
     толщиной в 5 пикселей */
  text-decoration-line: underline;
  text-decoration-color: yellow;
  text-decoration-thickness: 5px;
}

/* Зачёркиваем удалённые кусочки (del от deleted) статьи */
del {
  text-decoration: line-through;
}

Обра­тите вни­ма­ние на при­мер с text-decoration: underline. Бра­у­зеры и ЦСС посто­янно раз­ви­ва­ются: появ­ля­ются новые свой­ства, услож­ня­ются ста­рые. Новые вер­сии бра­у­зе­ров под­дер­жи­вают рас­ши­рен­ный син­так­сис text-decoration с ука­за­нием тол­щины, цвета и стиля линий, а ста­рые бра­у­зеры — нет. Поэтому чтобы под­чёр­ки­ва­ние рабо­тало во всех бра­у­зе­рах, мы сна­чала ука­зы­ваем ста­рый син­так­сис, как несго­ра­е­мый вари­ант, а ниже — новый: если бра­у­зер не пой­мёт новый син­так­сис, он исполь­зует преды­ду­щий вариант.

In CSS type size of the screen text usually lies in the range of 12 to 16 points and line spacing is 120–140 % of the type height size.

Иногда подчёркивание ссылок задают с помощью border или box‑shadow. Чтобы убрать такое подчёркивание, используют border: none или box‑shadow: none

Подчёркивание, зачёркивание и надчёркивание

Дополнительно оформить текст подчёркиванием, зачёркиванием или надчёркиванием можно с помощью text-decoration или border-bottom. Кроме того, в Хроме можно задать вид, стиль и цвет используемой линии:

/* Убираем подчёркивание у ссылок в шапке */
header a {
  text-decoration: none;
}

/* Подчёркиваем важные моменты (em от emphasis) в статье.
   Старые версии браузеров покажут чёрное подчёркивание */
em {
  text-decoration: underline;
  
  /* Новые браузеры покажут жёлтое подчёркивание
     толщиной в 5 пикселей */
  text-decoration-line: underline;
  text-decoration-color: yellow;
  text-decoration-thickness: 5px;
}

/* Зачёркиваем удалённые кусочки (del от deleted) статьи */
del {
  text-decoration: line-through;
}

Обратите внимание на пример с text-decoration: underline. Браузеры и ЦСС постоянно развиваются: появляются новые свойства, усложняются старые. Новые версии браузеров поддерживают расширенный синтаксис text-decoration с указанием толщины, цвета и стиля линий, а старые браузеры — нет. Поэтому чтобы подчёркивание работало во всех браузерах, мы сначала указываем старый синтаксис, как несгораемый вариант, а ниже — новый: если браузер не поймёт новый синтаксис, он использует предыдущий вариант.

In CSS type size of the screen text usually lies in the range of 12 to 16 points and line spacing is 120–140 % of the type height size.

Иногда подчёркивание ссылок задают с помощью border или box‑shadow. Чтобы убрать такое подчёркивание, используют border: none или box‑shadow: none

Не совсем корректный ЦСС

Раз­ные вер­сии бра­у­зе­ров под­дер­жи­вают раз­ные наборы ЦСС‑фич и свойств. Ска­жем, Хром уже под­дер­жи­вает вырав­ни­ва­ние послед­ней строки тек­ста с помо­щью text-align-last, а Сафари — пока нет. Если мы напи­шем text-align-last: right, то в Сафари ничего не изме­нится: бра­у­зер про­игно­ри­рует свой­ство, кото­рое пока не поддерживает.

Если в хотя бы одном из зна­че­ний состав­ного свой­ства будет опе­чатка или ошибка, бра­у­зер не смо­жет при­ме­нить свой­ство цели­ком. Если напи­сать border: 1px solid loud, бра­у­зер не доба­вит рамку эле­менту. Веб‑инспек­тор при этом пока­жет свой­ство зачёркнутым.

Сафари пока не поддерживает краткую запись для text‑decoration‑line, text‑decoration‑color и text‑decoration‑thickness. Поэтому в нём элемент останется без подчёркивания

В падинге опечатка: pixels вместо px. Элемент останется без отступов

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

Не совсем корректный ЦСС

Разные версии браузеров поддерживают разные наборы ЦСС‑фич и свойств. Скажем, Хром уже поддерживает выравнивание последней строки текста с помощью text-align-last, а Сафари — пока нет. Если мы напишем text-align-last: right, то в Сафари ничего не изменится: браузер проигнорирует свойство, которое пока не поддерживает.

Если в хотя бы одном из значений составного свойства будет опечатка или ошибка, браузер не сможет применить свойство целиком. Если написать border: 1px solid loud, браузер не добавит рамку элементу. Веб‑инспектор при этом покажет свойство зачёркнутым.

Сафари пока не поддерживает краткую запись для text‑decoration‑line, text‑decoration‑color и text‑decoration‑thickness. Поэтому в нём элемент останется без подчёркивания

В падинге опечатка: pixels вместо px. Элемент останется без отступов

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

Коворкафе

Радуга

Стерео

Тень

Чтобы доба­вить тень к тек­сту, исполь­зуют свой­ство text-shadow. Чтобы задать тень, ука­зы­вают её сме­ще­ние по гори­зон­тали и вер­ти­кали, радиус раз­мы­тия и цвет. Самое инте­рес­ное — можно разом задать несколько теней:

.neon {
  color: tomato;
  /* Задаём тень-свечение: с каждым шагом меняется
     радиус и цвет тени */
  text-shadow: 
    0 0 10px orangered,
    0 0 20px firebrick,
    0 0 40px maroon,
    0 0 80px darkred;
}

.rainbow {
  color: #fff;
  /* Задаём тень-радугу: с каждым шагом меняется
     цвет и смещение влево-вниз */
  text-shadow: -4px 4px #ef3550,
    -8px 8px #f48fb1,
    -12px 12px #7e57c2,
    -16px 16px #2196f3,
    -20px 20px #26c6da,
    -24px 24px #43a047,
    -28px 28px #eeff41,
    -32px 32px #f9a825,
    -36px 36px #ff5722;
}

.3d {
  color: #fff;
  /* Задаём 3д-тень: красную и синюю тени
     без размытия смещаем влево и вправо */
  text-shadow: -.06em 0 red,
    .06em 0 cyan;
}

Коворкафе

Радуга

Стерео

Тень

Чтобы добавить тень к тексту, используют свойство text-shadow. Чтобы задать тень, указывают её смещение по горизонтали и вертикали, радиус размытия и цвет. Самое интересное — можно разом задать несколько теней:

.neon {
  color: tomato;
  /* Задаём тень-свечение: с каждым шагом меняется
     радиус и цвет тени */
  text-shadow: 
    0 0 10px orangered,
    0 0 20px firebrick,
    0 0 40px maroon,
    0 0 80px darkred;
}

.rainbow {
  color: #fff;
  /* Задаём тень-радугу: с каждым шагом меняется
     цвет и смещение влево-вниз */
  text-shadow: -4px 4px #ef3550,
    -8px 8px #f48fb1,
    -12px 12px #7e57c2,
    -16px 16px #2196f3,
    -20px 20px #26c6da,
    -24px 24px #43a047,
    -28px 28px #eeff41,
    -32px 32px #f9a825,
    -36px 36px #ff5722;
}

.3d {
  color: #fff;
  /* Задаём 3д-тень: красную и синюю тени
     без размытия смещаем влево и вправо */
  text-shadow: -.06em 0 red,
    .06em 0 cyan;
}

Ино­гда текст нужно допол­ни­тельно укра­сить: накло­нить, повер­нуть или рас­кра­сить. Для при­мера сти­ли­зуем заго­ловки. Нач­нём с разметки:

<h2>216 лекций, 97 часов</h2>
<p>…</p>

Настроим шрифт и начертание:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold; /* Жирное начертание */
  text-transform: uppercase; /* Буквы приводим к заглавным */
  letter-spacing: -.025em; /* Уменьшаем межбуквенное расстояние */
}

Доба­вим тень, обводку и поме­няем цвет текста:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold;
  text-transform: uppercase;
  text-shadow: 5px 5px black; /* Добавляем чёрную тень, смещённую на 5 пк вправо-вниз */

  /* Добавляем двухпиксельную чёрную обводку буквам.
     
    Префикс -webkit- значит, что свойство пока не вошло
    в стандарты и работает в Хроме, Сафари, Эдже и Опере */
  -webkit-text-stroke: 2px black;
  color: white;
}

Иска­зим заголовки:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold;
  text-transform: uppercase;
  text-shadow: 5px 5px black;
  -webkit-text-stroke: 2px black;
  color: white;
  transform: skew(0, -8deg);
}

Убе­рём тень, доба­вим под­за­го­ловку градиент:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold;
  text-transform: uppercase;
  -webkit-text-stroke: 2px black;
  color: white;
  transform: skew(0, -8deg);
  /* linear-gradient — функция, генерирующая линейный градиент
    с заданным направлением, цветами и ключевыми точками */
  background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%);
}

Обре­жем гра­ди­ент по тексту:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold;
  text-transform: uppercase;
  -webkit-text-stroke: 2px black;
  color: white;
  transform: skew(0, -8deg);
  background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%);
  -webkit-background-clip: text; /* Обрезаем фон по тексту */
  color: transparent; /* И меняем цвет текста на прозрачный */
}

45 лек­ций, 9 часов

45 лекций, 9 часов

Иногда текст нужно дополнительно украсить: наклонить, повернуть или раскрасить. Для примера стилизуем заголовки. Начнём с разметки:

<h2>216 лекций, 97 часов</h2>
<p>…</p>

Настроим шрифт и начертание:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold; /* Жирное начертание */
  text-transform: uppercase; /* Буквы приводим к заглавным */
  letter-spacing: -.025em; /* Уменьшаем межбуквенное расстояние */
}

Добавим тень, обводку и поменяем цвет текста:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold;
  text-transform: uppercase;
  text-shadow: 5px 5px black; /* Добавляем чёрную тень, смещённую на 5 пк вправо-вниз */

  /* Добавляем двухпиксельную чёрную обводку буквам.
     
    Префикс -webkit- значит, что свойство пока не вошло
    в стандарты и работает в Хроме, Сафари, Эдже и Опере */
  -webkit-text-stroke: 2px black;
  color: white;
}

Исказим заголовки:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold;
  text-transform: uppercase;
  text-shadow: 5px 5px black;
  -webkit-text-stroke: 2px black;
  color: white;
  transform: skew(0, -8deg);
}

Уберём тень, добавим подзаголовку градиент:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold;
  text-transform: uppercase;
  -webkit-text-stroke: 2px black;
  color: white;
  transform: skew(0, -8deg);
  /* linear-gradient — функция, генерирующая линейный градиент
    с заданным направлением, цветами и ключевыми точками */
  background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%);
}

Обрежем градиент по тексту:

h2 {
  font-family: Roboto Condensed, sans-serif;
  font-weight: bold;
  text-transform: uppercase;
  -webkit-text-stroke: 2px black;
  color: white;
  transform: skew(0, -8deg);
  background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%);
  -webkit-background-clip: text; /* Обрезаем фон по тексту */
  color: transparent; /* И меняем цвет текста на прозрачный */
}

Перенос текста по словам

По умол­ча­нию бра­у­зер пере­но­сит слова, не вле­за­ю­щие в полосу, на новую строку. Но если слово очень длин­ное и не вме­ща­ется в строку, оно выле­зает за гра­ницы родителя.

Чтобы пере­но­сить длин­ные слова или ссылки, выле­за­ю­щие за полосу набора, исполь­зуют свой­ство overflow-wrap: слова меха­ни­че­ски раз­би­ва­ются ровно в том месте, где пере­стают вме­щаться в строку.

По умолчанию

Самое длинное существительное без дефиса — водогрязеторфопарафинолечение. А по сути — это просто лечение грязью, торфом и парафином.

overflow‑wrap: break‑word

Самое длинное существительное без дефиса — водогрязеторфопарафинолечение. А по сути — это просто лечение грязью, торфом и парафином.

Перенос текста по словам

По умолчанию браузер переносит слова, не влезающие в полосу, на новую строку. Но если слово очень длинное и не вмещается в строку, оно вылезает за границы родителя.

Чтобы переносить длинные слова или ссылки, вылезающие за полосу набора, используют свойство overflow-wrap: слова механически разбиваются ровно в том месте, где перестают вмещаться в строку.

По умолчанию

Самое длинное существительное без дефиса — водогрязеторфопарафинолечение. А по сути — это просто лечение грязью, торфом и парафином.

overflow‑wrap: break‑word

Самое длинное существительное без дефиса — водогрязеторфопарафинолечение. А по сути — это просто лечение грязью, торфом и парафином.

Перенос текста по слогам

Чтобы акку­ратно пере­но­сить слова с помо­щью дефи­сов, исполь­зуют свой­ство hyphens с клю­че­выми сло­вами manual (пере­но­сить только там, где ска­зано) и auto (пере­но­сить автоматически).

Авто­ма­ти­че­ские пере­носы осно­ваны на сло­ва­рях и пра­ви­лах деле­ния слов на слоги, поэтому без ука­за­ния языка доку­мента (<html lang="ru">) не работают.

Встро­ен­ный авто­ма­ти­че­ский пере­нос слов даёт непред­ска­зу­е­мый и не все­гда удач­ный резуль­тат, кото­рый отли­ча­ется от бра­у­зера к бра­у­зеру, поэтому обычно исполь­зуют hyphens: manual с «подсказками».

Автоматические переносы не понимают, что «водо­грязе­торфо­парафино­лечение» и «соковыжималку» лучше разбивать с учётом морфологии, а не по отдельным слогам.

hyphens: auto

Самое длинное существительное без дефиса – водогрязеторфопарафинолечение. А по сути — это просто лечение грязью, торфом или парафином.

Кофеварка или соковыжималка прекрасны.

hyphens: manual

Самое длинное суще­стви­тель­ное без дефиса – водо­грязе­торфо­парафино­лечение. А по сути — это просто лечение грязью, торфом или парафином.

Кофе­варка или соко­выжи­мал­ка прекрасны.

Перенос текста по слогам

Чтобы аккуратно переносить слова с помощью дефисов, используют свойство hyphens с ключевыми словами manual (переносить только там, где сказано) и auto (переносить автоматически).

Автоматические переносы основаны на словарях и правилах деления слов на слоги, поэтому без указания языка документа (<html lang="ru">) не работают.

Встроенный автоматический перенос слов даёт непредсказуемый и не всегда удачный результат, который отличается от браузера к браузеру, поэтому обычно используют hyphens: manual с «подсказками».

Автоматические переносы не понимают, что «водо­грязе­торфо­парафино­лечение» и «соковыжималку» лучше разбивать с учётом морфологии, а не по отдельным слогам.

hyphens: auto

Самое длинное существительное без дефиса – водогрязеторфопарафинолечение. А по сути — это просто лечение грязью, торфом или парафином.

Кофеварка или соковыжималка прекрасны.

hyphens: manual

Самое длинное суще­стви­тель­ное без дефиса – водо­грязе­торфо­парафино­лечение. А по сути — это просто лечение грязью, торфом или парафином.

Кофе­варка или соко­выжи­мал­ка прекрасны.

Инструменты расстановки переносов в тексте:

Чтобы под­ска­зать бра­у­зеру, где лучше пере­не­сти слово, исполь­зуют неви­ди­мые мяг­кие пере­носы &shy;. Их рас­став­ляют в тек­сте вруч­ную или с помо­щью гото­вых биб­лио­тек и программ:

<p>
  Самое длинное
  суще&shy;стви&shy;тель&shy;ное
  без дефиса – водо&shy;грязе&shy;
  торфо&shy;парафино&shy;лечение.
</p>

<p>
  Кофе&shy;варка,
  или соко&shy;выжи&shy;мал&shy;ка
  прекрасны.
</p>

Если уже рас­став­лен­ные мяг­кие пере­носы нужно запре­тить, напри­мер, при пере­ходе от мобиль­ной вер­сии к лап­топ­ной или гори­зон­таль­ной, доста­точно напи­сать hyphens: none.

Слова с дефи­сом бра­у­зеры могут авто­ма­ти­че­ски пере­но­сить по дефису. Чаще всего такие пере­носы — зло, потому что мешают читать и вос­при­ни­мать слово цели­ком: 22‑й, Поток‑2, 555‑33‑92 или военно‑мор­ской. В таких слу­чаях стоит заме­нять обыч­ный дефис - на неразрывный ‑

Чтобы подсказать браузеру, где лучше перенести слово, используют невидимые мягкие переносы &shy;. Их расставляют в тексте вручную или с помощью готовых библиотек и программ:

<p>
  Самое длинное
  суще&shy;стви&shy;тель&shy;ное
  без дефиса – водо&shy;грязе&shy;
  торфо&shy;парафино&shy;лечение.
</p>

<p>
  Кофе&shy;варка,
  или соко&shy;выжи&shy;мал&shy;ка
  прекрасны.
</p>

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

Слова с дефисом браузеры могут автоматически переносить по дефису. Чаще всего такие переносы — зло, потому что мешают читать и воспринимать слово целиком: 22‑й, Поток‑2, 555‑33‑92 или военно‑морской. В таких случаях стоит заменять обычный дефис - на неразрывный ‑

Инструменты расстановки переносов в тексте:

Богатая типографика

Когда на сайте не сле­дят за оформ­ле­нием тек­ста, это выгля­дит неряш­ливо и непро­фес­си­о­нально: упро­щён­ные кавычки, дефис вме­сто тире, пред­логи откле­и­лись и висят в воз­духе, а вме­сто спец­сим­во­лов урод­ли­вые ТМ и (c).

Наво­дить кра­соту — «бога­тую» типо­гра­фику — можно вруч­ную с рас­клад­кой Бир­мана или сер­ви­сами вроде Типо­графа Сту­дии Лебе­дева. Узнать пра­вила оформ­ле­ния тек­ста помо­жет спра­воч­ник Мильчина.

Богатая типографика

Когда на сайте не следят за оформлением текста, это выглядит неряшливо и непрофессионально: упрощённые кавычки, дефис вместо тире, предлоги отклеились и висят в воздухе, а вместо спецсимволов уродливые ТМ и (c).

Наводить красоту — «богатую» типографику — можно вручную с раскладкой Бирмана или сервисами вроде Типографа Студии Лебедева. Узнать правила оформления текста поможет справочник Мильчина.

Лайфхак: Эммет

Emmet — это рас­ши­ре­ние для редак­то­ров кода, кото­рое эко­но­мит кучу вре­мени при вёрстке.

Эммет поз­во­ляет писать корот­кие сокра­ще­ния, а нажа­тием Tab пре­вра­щать их в блоки ХТМЛ или ЦСС‑кода. Эммет пред­уста­нов­лен в боль­шин­стве редак­то­ров и песоч­ниц для кода.

Если Эммет не пред­уста­нов­лен в редак­тор, его навер­няка можно уста­но­вить туда само­сто­я­тельно. Это не сложно, а потра­чен­ное на уста­новку время оку­пится с лих­вой. Поищите инструк­ции по уста­новке для вашего редак­тора в Гугле.

Лайфхак: Эммет

Emmet — это расширение для редакторов кода, которое экономит кучу времени при вёрстке.

Эммет позволяет писать короткие сокращения, а нажатием Tab превращать их в блоки ХТМЛ или ЦСС‑кода. Эммет предустановлен в большинстве редакторов и песочниц для кода.

Если Эммет не предустановлен в редактор, его наверняка можно установить туда самостоятельно. Это не сложно, а потраченное на установку время окупится с лихвой. Поищите инструкции по установке для вашего редактора в Гугле.

Иллюстрации

Иллю­стра­ции вер­стают тегом img, где атри­бут src — это путь до файла‑картинки.

Можно под­клю­чить кар­тинку с дру­гого сайта:

<span><img src="https://upload.wikimedia.org/wikipedia/commons/David_-_Napoleon_crossing_the_Alps_-_Malmaison1.jpg"></span>

А можно сослаться на файл, лежа­щий рядом с вашей ХТМЛ‑стран­цей, напри­мер, в сосед­ней папке images. В этом слу­чае не пишут пол­ный адрес сайта, только путь до кар­тинки отно­си­тельно ХТМЛ‑файла:

<span><img src="img/napoleon.jpg"></span>

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

<span><img src="/img/1812/napoleon.jpg"></span>

По умолчанию ширина картинки на странице будет равна ширине файла картинки. Файлы в большом разрешении сломают вёрстку, поэтому лучше сразу сделать картинку «резиновой», задав ей display: block; и width в процентах от родителя.

Иллюстрации

Иллюстрации верстают тегом img, где атрибут src — это путь до файла‑картинки.

Можно подключить картинку с другого сайта:

<span><img src="https://upload.wikimedia.org/wikipedia/commons/David_-_Napoleon_crossing_the_Alps_-_Malmaison1.jpg"></span>

А можно сослаться на файл, лежащий рядом с вашей ХТМЛ‑странцей, например, в соседней папке images. В этом случае не пишут полный адрес сайта, только путь до картинки относительно ХТМЛ‑файла:

<span><img src="img/napoleon.jpg"></span>

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

<span><img src="/img/1812/napoleon.jpg"></span>

По умолчанию ширина картинки на странице будет равна ширине файла картинки. Файлы в большом разрешении сломают вёрстку, поэтому лучше сразу сделать картинку «резиновой», задав ей display: block; и width в процентах от родителя.

Михаил Врубель. Шестикрылый Серафим. 1904

Михаил Врубель. Шестикрылый Серафим. 1904

Иллюстрация с подписью

Иллю­стра­ции с подпи­ся­ми удобно вер­стать с помо­щью обёртки figure и подпи­си figcaption:

<figure class="image">
  <img src="images/vrubel.jpg">
  <figcaption>
    Михаил Врубель. Шестикрылый Серафим. 1904
  </figcaption>
</figure>
.image {
  width: 100%;
}

.image img {
  display: block;
  width: 100%;
  margin-bottom: 9px;
}

Обёртка создаёт из иллю­стра­ции и подпи­си еди­ный модуль. Стили кар­тинки и подпи­си настра­и­ва­ются один раз, затем модуль можно встра­и­вать в раз­ные места сайта.

Поста­вить под­пись сбоку можно флек­с­бок­сом — осо­быми свой­ствами для рас­кла­ды­ва­ния эле­мен­тов внутри кон­тей­нера. Мы раз­бе­рём флек­с­бокс дальше в книге, а пока можно запом­нить свой­ства как заклинание:

.image {
  display: flex; /* Ставим вложенные элементы в ряд */
  gap: 9px; /* Задаём отступы между элементами в ряду отдельный отступ у img больше не нужен */
}

.image figcaption {
  max-width: 30%; /* Подпись лучше ограничить по ширине  */
}

Михаил Врубель. Шестикрылый Серафим. 1904

Михаил Врубель. Шестикрылый Серафим. 1904

Иллюстрация с подписью

Иллюстрации с подписями удобно верстать с помощью обёртки figure и подписи figcaption:

<figure class="image">
  <img src="images/vrubel.jpg">
  <figcaption>
    Михаил Врубель. Шестикрылый Серафим. 1904
  </figcaption>
</figure>
.image {
  width: 100%;
}

.image img {
  display: block;
  width: 100%;
  margin-bottom: 9px;
}

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

Поставить подпись сбоку можно флексбоксом — особыми свойствами для раскладывания элементов внутри контейнера. Мы разберём флексбокс дальше в книге, а пока можно запомнить свойства как заклинание:

.image {
  display: flex; /* Ставим вложенные элементы в ряд */
  gap: 9px; /* Задаём отступы между элементами в ряду отдельный отступ у img больше не нужен */
}

.image figcaption {
  max-width: 30%; /* Подпись лучше ограничить по ширине  */
}

Атрибут alt

У тега img есть атри­бут alt — это аль­тер­на­тив­ный текст, кото­рый опи­сы­вает, что изоб­ра­жено на картинке.

Альт нужен на слу­чай, когда кар­тинка по ссылке недо­ступна, а ещё для помощи людям с про­бле­мами зре­ния, кото­рые исполь­зуют про­граммы для чте­ния с экрана.

<img 
  src="https://cats.com/persian/9001.jpg" 
  alt="Рыжая персидская кошка точит когти об когтеточку">

На альт смот­рят поис­ко­вые роботы, чтобы лучше понять содер­жи­мое стра­ницы и чтобы отдельно про­дви­гать кар­тинку по клю­че­вым сло­вам из альта в поиске.

Альты отли­ча­ются в зави­си­мо­сти от сайта и его задач. Для любовно соби­ра­е­мых вруч­ную ста­тей в блог не грех доба­вить в альты дета­лей, а для выдачи интер­нет‑мага­зина с тыся­чами това­ров разум­нее гене­ри­ро­вать альт под поис­ко­вое про­дви­же­ние, вклю­чать в него бренд и харак­те­ри­стики товара.

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

Атрибут alt

У тега img есть атрибут alt — это альтернативный текст, который описывает, что изображено на картинке.

Альт нужен на случай, когда картинка по ссылке недоступна, а ещё для помощи людям с проблемами зрения, которые используют программы для чтения с экрана.

<img 
  src="https://cats.com/persian/9001.jpg" 
  alt="Рыжая персидская кошка точит когти об когтеточку">

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

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

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

Таблицы

Таб­лица состоит из строк tr (table row) и ячеек td (table data cell) или th (table heading). Ячейки можно вкла­ды­вать только в строки и нельзя напря­мую в тег таблицы.

<table>
  <tr>
    <td>…</td>
    <td>…</td>
    <td>…</td>
  </tr>

  <tr>
    <td>…</td>
    <td>…</td>
    <td>…</td>
  </tr>
</table>

Для нагляд­но­сти под­све­тим гра­ницы ячеек:

td, th {
  background: #ebebeb;
}

Чтобы отдельно выде­лить шапку, тело или под­вал‑итог таб­лицы, исполь­зуют обёртки: thead, tbody и tfoot. С этими обёрт­ками проще ори­ен­ти­ро­ваться в коде и сти­ли­зо­вать отдель­ные части таблицы.

<table>
  <thead>
    <tr>
      <th>…</th>
      <th>…</th>
      <th>…</th>
    </tr>
  </thead>

  <tbody>
    <tr>…</tr>
    
    <tr>…</tr>
  </tbody>

  <tfoot>
    <tr>
      <td>…</td>
      <td>…</td>
      <td>…</td>
    </tr>
  </tfoot>
</table>

Атри­бут colspan ска­жет ячейке зани­мать несколько колонок:

<table>
  <thead>
    …
  </thead>

  <tbody>
    …
  </tbody>

  <tfoot>
    <tr>
      <td colspan="2">…</td>
      <td>…</td>
    </tr>
  </tfoot>
</table>

Атри­бут rowspan ска­жет зани­мать несколько строк:

<table>
  <thead>
    …
  </thead>

  <tbody>
    <tr>
      <td rowspan="2">…</td>
      <td>…</td>
      <td>…</td>
    </tr>

    <!-- 
      Если в предыдущей строке первая ячейка занимает две строки, то в следующей строке первую ячейку нужно убрать 
    -->
    <tr>
      <td>…</td>
      <td>…</td>
    </tr>
  </tbody>
  
  <tfoot>
    <tr>
      <td colspan="2">…</td>
      <td>…</td>
    </tr>
  </tfoot>
</table>

При сти­ли­за­ции про­стых таб­лиц можно обхо­диться без лиш­них клас­сов и сти­ли­зо­вать напря­мую по тегам — внутри пра­вильно свёр­стан­ной таб­лицы все­гда будет струк­тура из tr и вло­жен­ных td/th.

table {
  font-family: Helvetica, sans-serif;
  border-collapse: collapse; /* Особое свойство убирает отступы между ячейками */
}

/* Подкрасим нечётные строки таблицы */
tr:nth-child(odd) {
  background: #e5f5ff;
}

tfoot tr {
  border-top: 1px solid #e5f5ff;
}

td,
th {
  text-align: left;
  padding: 8px 4px;
}

tfoot td,
td:last-child,
th:last-child {
  text-align: right;  
}

Раньше из‑за технических ограничений целые сайты верстали как большую таблицу из колонок и строк. Сейчас появились более удобные инструменты вёрстки и таблицы используют только по их прямому назначению — для представления табличных данных

Таблицы

Таблица состоит из строк tr (table row) и ячеек td (table data cell) или th (table heading). Ячейки можно вкладывать только в строки и нельзя напрямую в тег таблицы.

<table>
  <tr>
    <td>…</td>
    <td>…</td>
    <td>…</td>
  </tr>

  <tr>
    <td>…</td>
    <td>…</td>
    <td>…</td>
  </tr>
</table>

Раньше из‑за технических ограничений целые сайты верстали как большую таблицу из колонок и строк. Сейчас появились более удобные инструменты вёрстки и таблицы используют только по их прямому назначению — для представления табличных данных

Для наглядности подсветим границы ячеек:

td, th {
  background: #ebebeb;
}

Чтобы отдельно выделить шапку, тело или подвал‑итог таблицы, используют обёртки: thead, tbody и tfoot. С этими обёртками проще ориентироваться в коде и стилизовать отдельные части таблицы.

<table>
  <thead>
    <tr>
      <th>…</th>
      <th>…</th>
      <th>…</th>
    </tr>
  </thead>

  <tbody>
    <tr>…</tr>
    
    <tr>…</tr>
  </tbody>

  <tfoot>
    <tr>
      <td>…</td>
      <td>…</td>
      <td>…</td>
    </tr>
  </tfoot>
</table>

Атрибут colspan скажет ячейке занимать несколько колонок:

<table>
  <thead>
    …
  </thead>

  <tbody>
    …
  </tbody>

  <tfoot>
    <tr>
      <td colspan="2">…</td>
      <td>…</td>
    </tr>
  </tfoot>
</table>

Атрибут rowspan скажет занимать несколько строк:

<table>
  <thead>
    …
  </thead>

  <tbody>
    <tr>
      <td rowspan="2">…</td>
      <td>…</td>
      <td>…</td>
    </tr>

    <!-- 
      Если в предыдущей строке первая ячейка занимает две строки, то в следующей строке первую ячейку нужно убрать 
    -->
    <tr>
      <td>…</td>
      <td>…</td>
    </tr>
  </tbody>
  
  <tfoot>
    <tr>
      <td colspan="2">…</td>
      <td>…</td>
    </tr>
  </tfoot>
</table>

При стилизации простых таблиц можно обходиться без лишних классов и стилизовать напрямую по тегам — внутри правильно свёрстанной таблицы всегда будет структура из tr и вложенных td/th.

table {
  font-family: Helvetica, sans-serif;
  border-collapse: collapse; /* Особое свойство убирает отступы между ячейками */
}

/* Подкрасим нечётные строки таблицы */
tr:nth-child(odd) {
  background: #e5f5ff;
}

tfoot tr {
  border-top: 1px solid #e5f5ff;
}

td,
th {
  text-align: left;
  padding: 8px 4px;
}

tfoot td,
td:last-child,
th:last-child {
  text-align: right;  
}
<table>
  <thead>
    <tr>
      <th>Год</th>
      <th>Страна</th>
      <th>Название</th>
      <th>Режиссёр</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>1968</td>
      <td>СССР</td>
      <td>Золотой телёнок</td>
      <td>Михаил Швейцер</td>
    </tr>

    <tr>
      <td>1969</td>
      <td>Чехословакия</td>
      <td>Командовать парадом буду я</td>
      <td>Ярослав Мах</td>
    </tr>

    <tr>
      <td>1974</td>
      <td>Венгрия</td>
      <td>Золотой телёнок</td>
      <td>Миклош Синетар</td>
    </tr>

    <tr>
      <td>1993</td>
      <td>Россия, Франция</td>
      <td>Мечты идиота</td>
      <td>Василий Пичул</td>
    </tr>

    <tr>
      <td>2006</td>
      <td>Россия</td>
      <td>Золотой телёнок</td>
      <td>Ульяна Шилкина</td>
    </tr>
  </tbody>
</table>
table {
  /*
  * Убираем отступы между
  * ячейками таблицы.
  */
  border-collapse: collapse;
}

td {
  /*
  * Настраиваем шрифт для ячеек:
  * Бюросанс, 14/17 пк.
  */
  font-family: Bureausans, Helvetica Neue,
    sans-serif;
  font-size: 14px;
  line-height: 17px;
  /*
  * Добавляем ячейкам отступы:
  * 14 пк справа, 7 пк сверху.
  */
  padding: 7px 14px 0 0;
  /*
  * Выравниваем содержимое ячеек
  * влево вверх.
  */
  text-align: left;
  vertical-align: top;
}

thead th {
  /*
  * Настраиваем шрифт для шапки:
  * жирный Бюросанс, 14/17 пк.
  */
  font-family: Bureausans, Helvetica Neue,
    sans-serif;
  font-weight: bold;
  font-size: 14px;
  line-height: 17px;
  /*
  * Добавляем нижнюю рамку к шапке.
  * Толщиной в 0,5 пикселя, сплошная,
  * черного цвета с прозрачностью 25%.
  */
  border-bottom: .5px solid rgba(0, 0, 0, .25);
  /*
  * Добавляем ячейкам шапки отступ
  * 14 пк справа.
  */
  padding: 0 14px 0 0;
  /*
  * Выравниваем содержимое шапки
  * влево вниз.
  */
  text-align: left;
  vertical-align: bottom;
}

ГодСтранаНазваниеРежиссёр
1968СССРЗоло­той телёнокМихаил Швейцер
1969ЧехословакияКоман­до­вать пара­дом буду яЯро­слав Мах
1974ВенгрияЗоло­той телёнокМик­лош Синетар
1993Рос­сия, ФранцияМечты идиотаВаси­лий Пичул
2006РоссияЗоло­той телёнокУльяна Шилкина

Свер­стаем ком­пакт­ную таб­лицу с экра­ни­за­ци­ями погони Остапа Бен­дера за граж­да­ни­ном Корейко.

Вни­ма­тельно изу­чите код, в нём встре­ча­ется несколько новых свойств и полез­ных приёмов.

Попробуйте скопировать код и поэксперименти­ровать со стилями таблицы

ГодСтранаНазваниеРежиссёр
1968СССРЗолотой телёнокМихаил Швейцер
1969ЧехословакияКомандовать парадом буду яЯрослав Мах
1974ВенгрияЗолотой телёнокМиклош Синетар
1993Россия, ФранцияМечты идиотаВасилий Пичул
2006РоссияЗолотой телёнокУльяна Шилкина

Сверстаем компактную таблицу с экранизациями погони Остапа Бендера за гражданином Корейко.

Внимательно изучите код, в нём встречается несколько новых свойств и полезных приёмов.

<table>
  <thead>
    <tr>
      <th>Год</th>
      <th>Страна</th>
      <th>Название</th>
      <th>Режиссёр</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>1968</td>
      <td>СССР</td>
      <td>Золотой телёнок</td>
      <td>Михаил Швейцер</td>
    </tr>

    <tr>
      <td>1969</td>
      <td>Чехословакия</td>
      <td>Командовать парадом буду я</td>
      <td>Ярослав Мах</td>
    </tr>

    <tr>
      <td>1974</td>
      <td>Венгрия</td>
      <td>Золотой телёнок</td>
      <td>Миклош Синетар</td>
    </tr>

    <tr>
      <td>1993</td>
      <td>Россия, Франция</td>
      <td>Мечты идиота</td>
      <td>Василий Пичул</td>
    </tr>

    <tr>
      <td>2006</td>
      <td>Россия</td>
      <td>Золотой телёнок</td>
      <td>Ульяна Шилкина</td>
    </tr>
  </tbody>
</table>
table {
  /*
  * Убираем отступы между
  * ячейками таблицы.
  */
  border-collapse: collapse;
}

td {
  /*
  * Настраиваем шрифт для ячеек:
  * Бюросанс, 14/17 пк.
  */
  font-family: Bureausans, Helvetica Neue,
    sans-serif;
  font-size: 14px;
  line-height: 17px;
  /*
  * Добавляем ячейкам отступы:
  * 14 пк справа, 7 пк сверху.
  */
  padding: 7px 14px 0 0;
  /*
  * Выравниваем содержимое ячеек
  * влево вверх.
  */
  text-align: left;
  vertical-align: top;
}

thead th {
  /*
  * Настраиваем шрифт для шапки:
  * жирный Бюросанс, 14/17 пк.
  */
  font-family: Bureausans, Helvetica Neue,
    sans-serif;
  font-weight: bold;
  font-size: 14px;
  line-height: 17px;
  /*
  * Добавляем нижнюю рамку к шапке.
  * Толщиной в 0,5 пикселя, сплошная,
  * черного цвета с прозрачностью 25%.
  */
  border-bottom: .5px solid rgba(0, 0, 0, .25);
  /*
  * Добавляем ячейкам шапки отступ
  * 14 пк справа.
  */
  padding: 0 14px 0 0;
  /*
  * Выравниваем содержимое шапки
  * влево вниз.
  */
  text-align: left;
  vertical-align: bottom;
}

Попробуйте скопировать код и поэксперименти­ровать со стилями таблицы

Аркадий Мильчин и Людмила Чельцова. Справочник издателя и автора

Вёрстка слож­ных таб­лиц тре­бует осо­бой вни­ма­тель­но­сти и акку­рат­но­сти, чтобы не запу­таться и не сло­мать струк­туру. Из‑за осо­бен­но­стей работы таб­лич­ных тегов, ино­гда при­хо­дится писать гро­мозд­кие стили, чтобы добиться результата.

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

Вни­ма­тельно изу­чите при­меры. Попро­буйте ско­пи­ро­вать код, допол­нить или изме­нить струк­туру и стили.

Таблица с иллюстрациями

Для сдвига эле­мен­тов группы «Виб­ро­устой­чи­вость» заво­дим отдель­ный класс. Чтобы запре­тить пере­носы в назва­ниях коло­нок и ячей­ках, исполь­зуем атри­бут nowrap, в отдель­ных зна­че­ниях — тег nobr.

<h3>
  Кнопки и переключатели кнопочные
</h3>

<table>
  <thead>
    <tr>
      <th width="30%">Показатели</th>
      <th nowrap>МПК 1-1, 2-1</th>
      <th nowrap>КН</th>
      <th nowrap>КМ А1-IV, 1-1, 2-1</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Схема коммутации</td>
      <td><img src="schemes/mpk.svg"></td>
      <td><img src="schemes/kn.svg"></td>
      <td><img src="schemes/km.svg"></td>
    </tr>

    <tr>
      <td>Напряжение коммутации, В</td>
      <td>30</td>
      <td>50</td>
      <td>=30~220</td>
    </tr>

    <tr>
      <td>Ток коммутации, A</td>
      <td>0,5</td>
      <td>1,5 (250 Вт)</td>
      <td>=4~3</td>
    </tr>

    <tr>
      <td>Температура, ˚C</td>
      <td>−60…+100</td>
      <td>−60…+85</td>
      <td>−60…+85</td>
    </tr>

    <tr>
      <td>Виброустойчивость:</td>
    </tr>

    <tr class="indented">
      <td>частота, Гц</td>
      <td>10…2 500</td>
      <td>5…80</td>
      <td>5…200</td>
    </tr>

    <tr class="indented">
      <td>ускорение, g, <nobr>м/с²</nobr></td>
      <td>12</td>
      <td>6</td>
      <td>7</td>
    </tr>

    <tr>
      <td>Ускорение при ударной нагрузке, g, <nobr>м/с²</nobr></td>
      <td>75</td>
      <td>7</td>
      <td>150</td>
    </tr>
  </tbody>
</table>
html {
  font-family: Helvetica;
}

/* Стилизуем заголовок под обычный курсивный текст. Честный заголовок, а не просто теги p и i лучше в плане семантики */
h3 {
  font-weight: normal;
  font-style: italic;
}

/* Базовая дизайнерская гигиена таблицы: ужимаем по ширине, выравниваем всё по левому верхнему краю */
table {
  border-collapse: collapse;
  width: min-content;
  font-size: inherit;
}

th,
td {
  vertical-align: top;
  text-align: left;
}

th,
tr td {
  padding: 18px 18px 0 0;
}

/* Нижняя граница шапки задаётся в её ячейках */
th {
  padding-top: 0;
  padding-bottom: 9px;
  border-bottom: 1px solid;
}

/* Правый падинг у последних ячеек не нужен */
td:last-child, 
th:last-child {
  padding-right: 0;
}

/* Трюки с группой, сдвигаем элементы группы правее */
tr.indented td:first-child {
  padding-left: 18px;
}

/* Уменьшаем верхние отступы группы и элементов */
tr:not(.indented) + tr.indented td {
  padding-top: 9px;
}

tr.indented td {
  padding-top: 13px;
}

Сдваивание длинной таблицы

Тут без хит­ро­стей, рас­по­ла­гаем таб­лицы внутри кон­тей­нера в ряд. Обра­тите вни­ма­ние на тег ниж­него индекса sub в подпи­си таб­лицы. Для верх­него индекса исполь­зу­ется его брат sup.

<div class="doubleTable">
  <table>
    <thead>
      <tr>
        <th>Тип панели</th>
        <th>Высота цилиндрической части баллона лампы, мм</th>
      </tr>
    </thead>            

    <tbody>
      <tr>
        <td nowrap>ПЛК9-Д-31</td>
        <td>28—31</td>
      </tr> 
      <tr>
        <td nowrap>ПЛК9-Д-40</td>
        <td>37—43</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Д-55</td>
        <td>52—58</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Д-60</td>
        <td>57—63</td>
      </tr>
    </tbody>
  </table>

  <table>
    <thead>
      <tr>
        <th>Тип панели</th>
        <th>Высота цилиндрической части баллона лампы, мм</th>
      </tr>
    </thead>            

    <tbody>
      <tr>
        <td nowrap>ПЛК9-Э-46</td>
        <td>28—34</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Э-55</td>
        <td>37—43</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Э-70</td>
        <td>52—58</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Э-75</td>
        <td>57—63</td>
      </tr>
    </tbody>
  </table>
</div>
<p>
  <i>Примечание.</i> Размер H<sub>макс</sub> (мм) — последние две цифры марки.
</p>

/* Основные стили таблицы как в прошлых примерах */

.doubleTable {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  flex-wrap: nowrap;
}

.doubleTable table:not(:last-child) {
  border-right: 1px solid;
  margin-right: 18px;
}

Таблица с группами

Поскольку групп много, проще по умол­ча­нию сдви­гать все строки влево, уби­рая сдвиг для заго­лов­ков групп и уве­ли­чи­вая их верх­ний отступ.

<table>
  <thead>
    <tr>
      <th rowspan="2">Вид литья и литейные сплавы (металлы)</th>
      <th classname="borderBottom" colspan="7">Площадь сплошной поверхности отливки, см²</th>
    </tr>
    <tr>
      <td>≤25</td>
      <td>>25</td>
      <td>>100</td>
      <td>>225</td>
      <td>>400</td>
      <td>>1000</td>
      <td>>1600</td>
    </tr>
  </thead>            

  <tbody>
      <tr classname="groupStart">
        <td colspan="8">В песчаные формы</td>
      </tr>
      <tr>
        <td>АЛ2</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>3,5</td>
        <td>4,0</td>
        <td>5,0</td>
        <td>6,0</td>
      </tr>

      <tr>
        <td>МЛ5</td>
        <td>3,0</td>
        <td>3,5</td>
        <td>4,0</td>
        <td>4,5</td>
        <td>5,0</td>
        <td>6,0</td>
        <td>7,0</td>
      </tr>

      <tr>
        <td>Чугун</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>4,0</td>
        <td>5,0</td>
        <td>6,0</td>
        <td>7,0</td>
      </tr>

      <tr classname="groupStart">
        <td colspan="8">В кокиль</td>
      </tr>
      <tr>
        <td>АЛ2</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>4,0</td>
        <td>4,0</td>
        <td></td>
        <td></td>
      </tr>

      <tr>
        <td>МЛ5</td>
        <td>3,0</td>
        <td>3,0</td>
        <td>4,0</td>
        <td>5,0</td>
        <td>6,0</td>
        <td></td>
        <td></td>
      </tr>

      <tr classname="groupStart">
        <td colspan="8">В оболочковые формы</td>
      </tr>
      <tr>
        <td>Алюминиевые</td>
        <td>2,0</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>4,0</td>
        <td>4,0</td>
        <td></td>
      </tr>
      <tr>
        <td>Медные</td>
        <td>2,0</td>
        <td>2,0</td>
        <td>3,0</td>
        <td>3,5</td>
        <td>4,0</td>
        <td>4,0</td>
        <td></td>
      </tr>
      <tr>
        <td>Сталь</td>
        <td>2,0</td>
        <td>2,0</td>
        <td>4,0</td>
        <td>4,0</td>
        <td>5,0</td>
        <td>6,0</td>
        <td></td>
      </tr>

      <tr classname="groupStart">
        <td colspan="8">Под давлением</td>
      </tr>
      <tr>
        <td>Оловянистые</td>
        <td>0,6</td>
        <td>0,7</td>
        <td>1,1</td>
        <td>1,5</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Цинковые</td>
        <td>0,8</td>
        <td>1,0</td>
        <td>1,5</td>
        <td>2,0</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Магниевые</td>
        <td>1,3</td>
        <td>1,8</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>4,0</td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Алюминиевые</td>
        <td>1,0</td>
        <td>1,5</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>4,0</td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Медные</td>
        <td>1,5</td>
        <td>2,0</td>
        <td>3,0</td>
        <td>3,5</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>

      <tr classname="groupStart">
        <td colspan="8">По выплавляемым моделям</td>
      </tr>
      <tr>
        <td>Сталь</td>
        <td>1,5</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
    </tbody>
  </table>
/* Основные стили таблицы как в прошлых примерах */

/* Конкретно в этой таблице нужна первая колонка пошире, задаём ей минимальную ширину */
th:first-child {
  min-width: 200px;
}

/* Увеличенный верхний отступ между группами */
tr.groupStart td {
  padding-top: 18px;
}

/* По умолчанию сдвигаем все первые ячейки, если они в теле таблицы и не вложены в .groupStart */
tbody tr:not(.groupStart) td:first-child {
  padding-left: 18px;
}

.borderBottom {
  border-bottom: 1px solid;
}

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

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

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

Таблица с иллюстрациями

Для сдвига элементов группы «Виброустойчивость» заводим отдельный класс. Чтобы запретить переносы в названиях колонок и ячейках, используем атрибут nowrap, в отдельных значениях — тег nobr.

<h3>
  Кнопки и переключатели кнопочные
</h3>

<table>
  <thead>
    <tr>
      <th width="30%">Показатели</th>
      <th nowrap>МПК 1-1, 2-1</th>
      <th nowrap>КН</th>
      <th nowrap>КМ А1-IV, 1-1, 2-1</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Схема коммутации</td>
      <td><img src="schemes/mpk.svg"></td>
      <td><img src="schemes/kn.svg"></td>
      <td><img src="schemes/km.svg"></td>
    </tr>

    <tr>
      <td>Напряжение коммутации, В</td>
      <td>30</td>
      <td>50</td>
      <td>=30~220</td>
    </tr>

    <tr>
      <td>Ток коммутации, A</td>
      <td>0,5</td>
      <td>1,5 (250 Вт)</td>
      <td>=4~3</td>
    </tr>

    <tr>
      <td>Температура, ˚C</td>
      <td>−60…+100</td>
      <td>−60…+85</td>
      <td>−60…+85</td>
    </tr>

    <tr>
      <td>Виброустойчивость:</td>
    </tr>

    <tr class="indented">
      <td>частота, Гц</td>
      <td>10…2 500</td>
      <td>5…80</td>
      <td>5…200</td>
    </tr>

    <tr class="indented">
      <td>ускорение, g, <nobr>м/с²</nobr></td>
      <td>12</td>
      <td>6</td>
      <td>7</td>
    </tr>

    <tr>
      <td>Ускорение при ударной нагрузке, g, <nobr>м/с²</nobr></td>
      <td>75</td>
      <td>7</td>
      <td>150</td>
    </tr>
  </tbody>
</table>

Аркадий Мильчин и Людмила Чельцова. Справочник издателя и автора

html {
  font-family: Helvetica;
}

/* Стилизуем заголовок под обычный курсивный текст. Честный заголовок, а не просто теги p и i лучше в плане семантики */
h3 {
  font-weight: normal;
  font-style: italic;
}

/* Базовая дизайнерская гигиена таблицы: ужимаем по ширине, выравниваем всё по левому верхнему краю */
table {
  border-collapse: collapse;
  width: min-content;
  font-size: inherit;
}

th,
td {
  vertical-align: top;
  text-align: left;
}

th,
tr td {
  padding: 18px 18px 0 0;
}

/* Нижняя граница шапки задаётся в её ячейках */
th {
  padding-top: 0;
  padding-bottom: 9px;
  border-bottom: 1px solid;
}

/* Правый падинг у последних ячеек не нужен */
td:last-child, 
th:last-child {
  padding-right: 0;
}

/* Трюки с группой, сдвигаем элементы группы правее */
tr.indented td:first-child {
  padding-left: 18px;
}

/* Уменьшаем верхние отступы группы и элементов */
tr:not(.indented) + tr.indented td {
  padding-top: 9px;
}

tr.indented td {
  padding-top: 13px;
}

Сдваивание длинной таблицы

Тут без хитростей, располагаем таблицы внутри контейнера в ряд. Обратите внимание на тег нижнего индекса sub в подписи таблицы. Для верхнего индекса используется его брат sup.

<div class="doubleTable">
  <table>
    <thead>
      <tr>
        <th>Тип панели</th>
        <th>Высота цилиндрической части баллона лампы, мм</th>
      </tr>
    </thead>            

    <tbody>
      <tr>
        <td nowrap>ПЛК9-Д-31</td>
        <td>28—31</td>
      </tr> 
      <tr>
        <td nowrap>ПЛК9-Д-40</td>
        <td>37—43</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Д-55</td>
        <td>52—58</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Д-60</td>
        <td>57—63</td>
      </tr>
    </tbody>
  </table>

  <table>
    <thead>
      <tr>
        <th>Тип панели</th>
        <th>Высота цилиндрической части баллона лампы, мм</th>
      </tr>
    </thead>            

    <tbody>
      <tr>
        <td nowrap>ПЛК9-Э-46</td>
        <td>28—34</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Э-55</td>
        <td>37—43</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Э-70</td>
        <td>52—58</td>
      </tr>
      <tr>
        <td nowrap>ПЛК9-Э-75</td>
        <td>57—63</td>
      </tr>
    </tbody>
  </table>
</div>
<p>
  <i>Примечание.</i> Размер H<sub>макс</sub> (мм) — последние две цифры марки.
</p>

/* Основные стили таблицы как в прошлых примерах */

.doubleTable {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  flex-wrap: nowrap;
}

.doubleTable table:not(:last-child) {
  border-right: 1px solid;
  margin-right: 18px;
}

Таблица с группами

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

<table>
  <thead>
    <tr>
      <th rowspan="2">Вид литья и литейные сплавы (металлы)</th>
      <th classname="borderBottom" colspan="7">Площадь сплошной поверхности отливки, см²</th>
    </tr>
    <tr>
      <td>≤25</td>
      <td>>25</td>
      <td>>100</td>
      <td>>225</td>
      <td>>400</td>
      <td>>1000</td>
      <td>>1600</td>
    </tr>
  </thead>            

  <tbody>
      <tr classname="groupStart">
        <td colspan="8">В песчаные формы</td>
      </tr>
      <tr>
        <td>АЛ2</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>3,5</td>
        <td>4,0</td>
        <td>5,0</td>
        <td>6,0</td>
      </tr>

      <tr>
        <td>МЛ5</td>
        <td>3,0</td>
        <td>3,5</td>
        <td>4,0</td>
        <td>4,5</td>
        <td>5,0</td>
        <td>6,0</td>
        <td>7,0</td>
      </tr>

      <tr>
        <td>Чугун</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>4,0</td>
        <td>5,0</td>
        <td>6,0</td>
        <td>7,0</td>
      </tr>

      <tr classname="groupStart">
        <td colspan="8">В кокиль</td>
      </tr>
      <tr>
        <td>АЛ2</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>4,0</td>
        <td>4,0</td>
        <td></td>
        <td></td>
      </tr>

      <tr>
        <td>МЛ5</td>
        <td>3,0</td>
        <td>3,0</td>
        <td>4,0</td>
        <td>5,0</td>
        <td>6,0</td>
        <td></td>
        <td></td>
      </tr>

      <tr classname="groupStart">
        <td colspan="8">В оболочковые формы</td>
      </tr>
      <tr>
        <td>Алюминиевые</td>
        <td>2,0</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>4,0</td>
        <td>4,0</td>
        <td></td>
      </tr>
      <tr>
        <td>Медные</td>
        <td>2,0</td>
        <td>2,0</td>
        <td>3,0</td>
        <td>3,5</td>
        <td>4,0</td>
        <td>4,0</td>
        <td></td>
      </tr>
      <tr>
        <td>Сталь</td>
        <td>2,0</td>
        <td>2,0</td>
        <td>4,0</td>
        <td>4,0</td>
        <td>5,0</td>
        <td>6,0</td>
        <td></td>
      </tr>

      <tr classname="groupStart">
        <td colspan="8">Под давлением</td>
      </tr>
      <tr>
        <td>Оловянистые</td>
        <td>0,6</td>
        <td>0,7</td>
        <td>1,1</td>
        <td>1,5</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Цинковые</td>
        <td>0,8</td>
        <td>1,0</td>
        <td>1,5</td>
        <td>2,0</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Магниевые</td>
        <td>1,3</td>
        <td>1,8</td>
        <td>2,5</td>
        <td>3,0</td>
        <td>4,0</td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Алюминиевые</td>
        <td>1,0</td>
        <td>1,5</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>4,0</td>
        <td></td>
        <td></td>
      </tr>
      <tr>
        <td>Медные</td>
        <td>1,5</td>
        <td>2,0</td>
        <td>3,0</td>
        <td>3,5</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>

      <tr classname="groupStart">
        <td colspan="8">По выплавляемым моделям</td>
      </tr>
      <tr>
        <td>Сталь</td>
        <td>1,5</td>
        <td>2,0</td>
        <td>2,5</td>
        <td>3,0</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
    </tbody>
  </table>
/* Основные стили таблицы как в прошлых примерах */

/* Конкретно в этой таблице нужна первая колонка пошире, задаём ей минимальную ширину */
th:first-child {
  min-width: 200px;
}

/* Увеличенный верхний отступ между группами */
tr.groupStart td {
  padding-top: 18px;
}

/* По умолчанию сдвигаем все первые ячейки, если они в теле таблицы и не вложены в .groupStart */
tbody tr:not(.groupStart) td:first-child {
  padding-left: 18px;
}

.borderBottom {
  border-bottom: 1px solid;
}

Лучше не исполь­зо­вать таб­лич­ные теги в пап­ках писем и дру­гих интер­фейс­ных спис­ках, иначе код ста­нет гро­мозд­ким и неуправ­ля­е­мым. Тех­но­ло­гич­нее исполь­зо­вать обыч­ные дивы.

ХТМЛ‑таб­лицы — хруп­кий мате­риал. Они под­хо­дят для спра­воч­ных таб­лиц, но за пре­де­лами этой задачи начи­на­ются про­блемы. Ори­ен­ти­ро­ваться в струк­туре таб­лич­ных тегах заметно слож­нее, чем в струк­туре обыч­ных дивов. В таб­ли­цах тяжело управ­лять отсту­пами между ячей­ками и выстро­ить надёж­ную и гиб­кую систему отступов.

В интер­фей­сах лучше не горо­дить таб­лич­ную струк­туру, а исполь­зо­вать обыч­ные дивы. Управ­лять их отсту­пами и пози­цией гораздо проще — в дивах рабо­тают все ЦСС‑свой­ства, в отли­чие от таб­лич­ных тегов.

<h2>Входящие</h2>

<div class="mailbox">
  <div class="mailRow">
    <div class="mailRow__preview">
      <input type="checkbox">
      <button type="button" class="btnStar">☆</button>
      <span class="mailRow__sender">Яндекс.Практикум</span>
      <strong class="mailRow__subj">Платёж успешно выполнен</strong>
      <span class="mailRow__date">13 августа</span>
    </div>
  </div>

  <div class="mailRow is__active">
    <div class="mailRow__preview">
      <input type="checkbox">
      <button type="button" class="btnStar">☆</button>
      <span class="mailRow__sender">hoster.by</span>
      <span class="mailRow__subj">Дарим наггетсы за участие в опросе</span>
      <span class="mailRow__date">10 августа</span>
    </div>

    <div class="mailRow__in">
      <h1>Дарим наггетсы за участие в опросе</h1>
      <p>Привет! Вы нас может уже и не помните, а вот мы вас частенько вспоминаем.</p>
      <p>Нет времени объяснять — нам нужно всего 6 минут вашего времени, чтобы узнать, как вы к нам относитесь. За ответы на вопросы подарим промокод на бесплатные наггетсы от наших друзей из Burger King.</p>
      <a href="#">Ответить на вопросы</a>
    </div>
  </div>

  <div class="mailRow">
    <div class="mailRow__preview">
      <input type="checkbox">
      <button type="button" class="btnStar">★</button>
      <span class="mailRow__sender">Фонотека</span>
      <span class="mailRow__subj">Кругосветка за четыре дня: -30% на немецкий краут, американский блюз, японский винил</span>
      <span class="mailRow__date">9 августа</span>
    </div>
  </div>

  <-- … -->
</div>
body {
  font-family: Helvetica;
}

.mailbox {
  font-size: .8em;
}

.mailRow {
  border-bottom: 1px solid rgba(0, 0, 0, .05);
}

.mailRow.is__active {
  background: rgba(0, 0, 0, .05)
}

.mailRow__preview {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: .15em;
  flex-wrap: nowrap;
  white-space: pre-wrap;
  padding: .3em .5em .7em;
  box-sizing: border-box;
}

.mailRow__sender {
  width: 25%;
}

/* Тут рюк с многоточием для длинных строк */
.mailRow__subj {
  flex-grow: 1;

  /* Ограничиваем ширину */
  max-width: 50%;

  /* Запрещаем перенос строки */
  white-space: nowrap;

  /* Обрезаем всё, что вылезло за ширину, а текст обрезаем многоточием */
  overflow: hidden;
  text-overflow: ellipsis;
}

.mailRow__date {
  flex-grow: 1;
  text-align: right;
}

.mailRow__in {
  width: 100%;
  font-size: .9em;
  padding: 1em 1em 2em;
  box-sizing: border-box;
}

.mailRow h1 {
  margin-top: 0;
}

.mailRow__in a {
  color: inherit;
}

/* Сбрасываем стандартные стили кнопки */
.btnStar {
  border: none;
  background: none;
}

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

ХТМЛ‑таблицы — хрупкий материал. Они подходят для справочных таблиц, но за пределами этой задачи начинаются проблемы. Ориентироваться в структуре табличных тегах заметно сложнее, чем в структуре обычных дивов. В таблицах тяжело управлять отступами между ячейками и выстроить надёжную и гибкую систему отступов.

В интерфейсах лучше не городить табличную структуру, а использовать обычные дивы. Управлять их отступами и позицией гораздо проще — в дивах работают все ЦСС‑свойства, в отличие от табличных тегов.

<h2>Входящие</h2>

<div class="mailbox">
  <div class="mailRow">
    <div class="mailRow__preview">
      <input type="checkbox">
      <button type="button" class="btnStar">☆</button>
      <span class="mailRow__sender">Яндекс.Практикум</span>
      <strong class="mailRow__subj">Платёж успешно выполнен</strong>
      <span class="mailRow__date">13 августа</span>
    </div>
  </div>

  <div class="mailRow is__active">
    <div class="mailRow__preview">
      <input type="checkbox">
      <button type="button" class="btnStar">☆</button>
      <span class="mailRow__sender">hoster.by</span>
      <span class="mailRow__subj">Дарим наггетсы за участие в опросе</span>
      <span class="mailRow__date">10 августа</span>
    </div>

    <div class="mailRow__in">
      <h1>Дарим наггетсы за участие в опросе</h1>
      <p>Привет! Вы нас может уже и не помните, а вот мы вас частенько вспоминаем.</p>
      <p>Нет времени объяснять — нам нужно всего 6 минут вашего времени, чтобы узнать, как вы к нам относитесь. За ответы на вопросы подарим промокод на бесплатные наггетсы от наших друзей из Burger King.</p>
      <a href="#">Ответить на вопросы</a>
    </div>
  </div>

  <div class="mailRow">
    <div class="mailRow__preview">
      <input type="checkbox">
      <button type="button" class="btnStar">★</button>
      <span class="mailRow__sender">Фонотека</span>
      <span class="mailRow__subj">Кругосветка за четыре дня: -30% на немецкий краут, американский блюз, японский винил</span>
      <span class="mailRow__date">9 августа</span>
    </div>
  </div>

  <-- … -->
</div>
body {
  font-family: Helvetica;
}

.mailbox {
  font-size: .8em;
}

.mailRow {
  border-bottom: 1px solid rgba(0, 0, 0, .05);
}

.mailRow.is__active {
  background: rgba(0, 0, 0, .05)
}

.mailRow__preview {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: .15em;
  flex-wrap: nowrap;
  white-space: pre-wrap;
  padding: .3em .5em .7em;
  box-sizing: border-box;
}

.mailRow__sender {
  width: 25%;
}

/* Тут рюк с многоточием для длинных строк */
.mailRow__subj {
  flex-grow: 1;

  /* Ограничиваем ширину */
  max-width: 50%;

  /* Запрещаем перенос строки */
  white-space: nowrap;

  /* Обрезаем всё, что вылезло за ширину, а текст обрезаем многоточием */
  overflow: hidden;
  text-overflow: ellipsis;
}

.mailRow__date {
  flex-grow: 1;
  text-align: right;
}

.mailRow__in {
  width: 100%;
  font-size: .9em;
  padding: 1em 1em 2em;
  box-sizing: border-box;
}

.mailRow h1 {
  margin-top: 0;
}

.mailRow__in a {
  color: inherit;
}

/* Сбрасываем стандартные стили кнопки */
.btnStar {
  border: none;
  background: none;
}

Формы

ХТМЛ поз­во­ляет свер­стать и базово настро­ить пове­де­ние формы.

Чтобы ожи­вить форму и отправ­лять дан­ные из неё на сер­вер, пона­до­бится сам сер­вер и допол­ни­тель­ное программирование.

Мы не будем подробно оста­нав­ли­ваться на про­грам­ми­ро­ва­нии форм — это выхо­дит далеко за рамки книги. Но научимся соби­рать дан­ные формы с помо­щью сер­виса «Формкип».

Можно и вовсе не вер­стать форму, а исполь­зо­вать сер­висы‑кон­струк­торы. В них вы соби­ра­ете форму из типо­вых эле­мен­тов, а затем полу­ча­ете кусок кода для встра­и­ва­ния формы на сайт. Чаще всего, формы соби­рают в Гугль‑ и Яндекс‑фор­мах или в «Формкипе».

Формы

ХТМЛ позволяет сверстать и базово настроить поведение формы.

Чтобы оживить форму и отправлять данные из неё на сервер, понадобится сам сервер и дополнительное программирование.

Мы не будем подробно останавливаться на программировании форм — это выходит далеко за рамки книги. Но научимся собирать данные формы с помощью сервиса «Формкип».

Можно и вовсе не верстать форму, а использовать сервисы‑конструкторы. В них вы собираете форму из типовых элементов, а затем получаете кусок кода для встраивания формы на сайт. Чаще всего, формы собирают в Гугль‑ и Яндекс‑формах или в «Формкипе».

Форму вер­стают внутри одно­имён­ного тега‑обёртки. Это облег­чит жизнь, когда дело дой­дёт до отправки дан­ных формы.

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

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

<form>
  <!-- placeholder видно в поле до начала ввода -->
  <input type="text" placeholder="Логин" />

  <!-- type="password" прячет вводимые символы -->
  <input type="password" placeholder="Пароль" />

  <button type="submit">Войти</button>
</form>
form {
  /* Расставим элементы в колонку с отступами */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5em;
}

/* Особым псевдоклассом перекрасим плейсхолдеры */
::placeholder {
  color: #ddd;
}

/* Выделим общие стили для полей и кнопок */
input,
button {
  font-size: 1.8em;
  border-radius: 8px;
}

input {
  padding: 0.3em;
  border: 2px solid #ddd;
}

button {
  padding: 0.3em 1.15em;

  /* Уберём обводку, которую браузер добавляет кнопкам по умолчанию */
  border: none;
  background: #ff5722;
  color: #fff;
}

Форму верстают внутри одноимённого тега‑обёртки. Это облегчит жизнь, когда дело дойдёт до отправки данных формы.

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

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

<form>
  <!-- placeholder видно в поле до начала ввода -->
  <input type="text" placeholder="Логин" />

  <!-- type="password" прячет вводимые символы -->
  <input type="password" placeholder="Пароль" />

  <button type="submit">Войти</button>
</form>
form {
  /* Расставим элементы в колонку с отступами */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: .5em;
}

/* Особым псевдоклассом перекрасим плейсхолдеры */
::placeholder {
  color: #ddd;
}

/* Выделим общие стили для полей и кнопок */
input,
button {
  font-size: 1.8em;
  border-radius: 8px;
}

input {
  padding: 0.3em;
  border: 2px solid #ddd;
}

button {
  padding: 0.3em 1.15em;

  /* Уберём обводку, которую браузер добавляет кнопкам по умолчанию */
  border: none;
  background: #ff5722;
  color: #fff;
}

Элементы ввода, или инпуты

В ХТМЛ‑фор­мах эле­менты ввода дают чело­веку вве­сти или уточ­нить дан­ные: поля, чек­боксы, радиокнопки, выпа­да­ю­щие списки и дру­гие эле­менты. Раз­ра­бот­чики назы­вают эле­менты ввода инпу­тами, потому что почти все­гда это тег input.

Инпуты отли­ча­ются типами — атри­бу­том type. Тип вли­яет на внеш­ний вид инпута и фор­ма­ти­ро­ва­ние вво­ди­мых данных.

Инпут с type="password" скроет вво­ди­мый текст, type="number" не даст вве­сти буквы, а type="date" пока­жет фор­мат ввода даты, а в неко­то­рых бра­у­зе­рах доба­вит кнопку вызова кален­да­рика. Фай­ло­вый инпут под­дер­жи­вает пере­тя­ги­ва­ние файла на себя, а инпут цвета откры­вает палитру выбора цвета.

Особ­ня­ком среди инпу­тов стоят чек­боксы, радиокнопки и выпа­да­ю­щие списки. Их вёрстка отли­ча­ется от вёрстки осталь­ных инпу­тов, поэтому далее мы раз­бе­рём их отдельно.

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

<input type="text">

type="text"

type="number"

type="password"

type="date"

type="datetime‑local"

type="time"

type="file"

type="color"

type="range"

Элементы ввода, или инпуты

В ХТМЛ‑формах элементы ввода дают человеку ввести или уточнить данные: поля, чекбоксы, радиокнопки, выпадающие списки и другие элементы. Разработчики называют элементы ввода инпутами, потому что почти всегда это тег input.

Инпуты отличаются типами — атрибутом type. Тип влияет на внешний вид инпута и форматирование вводимых данных.

Инпут с type="password" скроет вводимый текст, type="number" не даст ввести буквы, а type="date" покажет формат ввода даты, а в некоторых браузерах добавит кнопку вызова календарика. Файловый инпут поддерживает перетягивание файла на себя, а инпут цвета открывает палитру выбора цвета.

Особняком среди инпутов стоят чекбоксы, радиокнопки и выпадающие списки. Их вёрстка отличается от вёрстки остальных инпутов, поэтому далее мы разберём их отдельно.

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

<input type="text">

type="text"

type="number"

type="password"

type="date"

type="datetime‑local"

type="time"

type="file"

type="color"

type="range"

Текстобласть

Мно­го­строч­ное тек­сто­вое поле созда­ётся тегом textarea. Тег нужно закры­вать, в отли­чие от неза­кры­ва­ю­ще­гося input. Внутрь textarea можно напи­сать текст, чтобы зара­нее запол­нить тек­сто­бласть. Напи­сан­ный текст сохра­нит все про­белы и пере­носы строк.

У тега textarea есть два уни­каль­ных атри­бута: rows и cols задают высоту в стро­ках и ширину в сим­во­лах в соот­вет­ствии с раз­ме­ром шрифта.

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

Огра­ни­чить направ­ле­ние или отклю­чить изме­не­ние раз­мера можно ЦСС‑свой­ством resize со зна­че­ни­ями horizontal, vertical или none.

<textarea 
  rows="5" 
  cols="20"
  placeholder="Комментарий">
</textarea>
textarea {
  min-width: 320px;
  max-width: 100%;

  min-height: 150px;
  max-height: 250px;
}

Текстобласть

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

У тега textarea есть два уникальных атрибута: rows и cols задают высоту в строках и ширину в символах в соответствии с размером шрифта.

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

Ограничить направление или отключить изменение размера можно ЦСС‑свойством resize со значениями horizontal, vertical или none.

<textarea 
  rows="5" 
  cols="20"
  placeholder="Комментарий">
</textarea>
textarea {
  min-width: 320px;
  max-width: 100%;

  min-height: 150px;
  max-height: 250px;
}

Чекбокс

Чек­бокс — это эле­мент выбора объ­екта или уточ­не­ния дей­ствия, он отве­чает на вопрос «с чем именно» или «как именно» выпол­нить основ­ное дей­ствие. Чек­бокс выгля­дит как галочка, вклю­ча­ю­щая объ­ект, свой­ство или опцию.

Сам по себе <input type="checkbox"> создаёт только квад­ра­тик, в кото­рый можно поста­вить галочку. Под­пись вер­стают отдель­ным тегом или про­сто текстом.

В при­мере для вёрстки чек­бокса исполь­зу­ется обёртка label. Клик по тек­сту внутри лей­бла будет рабо­тать так же, как клик в сам эле­мент управления.

Атри­бут checked ста­вит галочку по умолчанию.

Илья Бирман. Пользовательский интерфейс. Чекбокс

<form>
  <label>
    <input type="checkbox" checked>
    Соглашаюсь на…
  </label>
</form>
input[type="checkbox"] {
  margin-right: 5px;
}

Чекбокс

Чекбокс — это элемент выбора объекта или уточнения действия, он отвечает на вопрос «с чем именно» или «как именно» выполнить основное действие. Чекбокс выглядит как галочка, включающая объект, свойство или опцию.

Сам по себе <input type="checkbox"> создаёт только квадратик, в который можно поставить галочку. Подпись верстают отдельным тегом или просто текстом.

В примере для вёрстки чекбокса используется обёртка label. Клик по тексту внутри лейбла будет работать так же, как клик в сам элемент управления.

Атрибут checked ставит галочку по умолчанию.

Илья Бирман. Пользовательский интерфейс. Чекбокс

<form>
  <label>
    <input type="checkbox" checked>
    Соглашаюсь на…
  </label>
</form>
input[type="checkbox"] {
  margin-right: 5px;
}

Радиокнопки

Радиокнопка — это эле­мент выбора объ­екта или уточ­не­ния дей­ствия из несколь­ких вари­ан­тов. Эле­мент отве­чает на вопрос «с чем именно» или «как именно» выпол­нить действие.

Радиокнопки объ­еди­няют в группу кно­пок — объ­ек­тов, свойств или опций, — из кото­рых может быть выбрана и нажата только одна.

Радиокнопки в вёрстке — это инпуты с type="radio". Чтобы объ­еди­нить радиокнопки в группу, инпу­там задают оди­на­ко­вый атри­бут name. Среди радиокно­пок с оди­на­ко­вым name бра­у­зер даст нажать только одну.

Атри­бут checked выби­рает радиокнопку по умолчанию.

Илья Бирман. Пользовательский интерфейс. Радиокнопки

<form>
  <h4>Доставка</h4>
  <label>
    <input type="radio" name="delivery" checked> Почтой
  </label>

  <label>
    <input type="radio" name="delivery"> Курьером
  </label>

  <label>
    <input type="radio" name="delivery"> В пункт выдачи
  </label>

  <h4>Оплата</h4>
  <label>
    <input type="radio" name="payment" checked> Картой на сайте
  </label>

  <label>
    <input type="radio" name="payment"> При получении
  </label>
</form>

Доставка

Оплата

Радиокнопки

Радиокнопка — это элемент выбора объекта или уточнения действия из нескольких вариантов. Элемент отвечает на вопрос «с чем именно» или «как именно» выполнить действие.

Радиокнопки объединяют в группу кнопок — объектов, свойств или опций, — из которых может быть выбрана и нажата только одна.

Радиокнопки в вёрстке — это инпуты с type="radio". Чтобы объединить радиокнопки в группу, инпутам задают одинаковый атрибут name. Среди радиокнопок с одинаковым name браузер даст нажать только одну.

Атрибут checked выбирает радиокнопку по умолчанию.

Илья Бирман. Пользовательский интерфейс. Радиокнопки

Доставка

Оплата

<form>
  <h4>Доставка</h4>
  <label>
    <input type="radio" name="delivery" checked> Почтой
  </label>

  <label>
    <input type="radio" name="delivery"> Курьером
  </label>

  <label>
    <input type="radio" name="delivery"> В пункт выдачи
  </label>

  <h4>Оплата</h4>
  <label>
    <input type="radio" name="payment" checked> Картой на сайте
  </label>

  <label>
    <input type="radio" name="payment"> При получении
  </label>
</form>

Переключатель

Пере­клю­ча­тель — вари­ант группы радиокно­пок. В ХТМЛ нет отдель­ного тега для пере­клю­ча­теля, мы сове­туем вер­стать его с помо­щью трюка с input type="radio".

Трюк осно­ван на стро­гой струк­туре вёрстки. Исполь­зуем тег label, внутри инпут‑радио и див — кнопка пере­клю­ча­теля. Тег label по клику акти­ви­рует нахо­дя­щийся внутри инпут, подроб­нее мы раз­бе­рём это дальше в книге. А пока в ЦСС спря­чем сами инпуты и будем под­кра­ши­вать бли­жай­шего соседа актив­ного инпута.

Спря­тан­ные инпуты не пере­стают рабо­тать, клик по обёртке label будет их акти­ви­ро­вать и при­ме­нять стили для дива‑соседа.

<div class="switch">
  <label>
    <input type="radio" name="transport" checked>
    <div class="switch__item">Самолётом</div>
  </label>

  <label>
    <input type="radio" name="transport">
    <div class="switch__item">Поездом</div>
  </label>

  <label>
    <input type="radio" name="transport">
    <div class="switch__item">Машиной</div>
  </label>
</div>
.switch {
  display: flex; /* Ставим элементы в ряд */
}

.switch__item {
  padding: 5px 10px;
}

.switch input {
  display: none; /* Спрятанный инпут не перестаёт работать */
}

/* Применяем стили для соседа «чекнутого» инпута */
.switch input:checked + .switch__item {
  background: #00aff0;
  color: white;
}

Переключатель

Переключатель — вариант группы радиокнопок. В ХТМЛ нет отдельного тега для переключателя, мы советуем верстать его с помощью трюка с input type="radio".

Трюк основан на строгой структуре вёрстки. Используем тег label, внутри инпут‑радио и див — кнопка переключателя. Тег label по клику активирует находящийся внутри инпут, подробнее мы разберём это дальше в книге. А пока в ЦСС спрячем сами инпуты и будем подкрашивать ближайшего соседа активного инпута.

Спрятанные инпуты не перестают работать, клик по обёртке label будет их активировать и применять стили для дива‑соседа.

<div class="switch">
  <label>
    <input type="radio" name="transport" checked>
    <div class="switch__item">Самолётом</div>
  </label>

  <label>
    <input type="radio" name="transport">
    <div class="switch__item">Поездом</div>
  </label>

  <label>
    <input type="radio" name="transport">
    <div class="switch__item">Машиной</div>
  </label>
</div>
.switch {
  display: flex; /* Ставим элементы в ряд */
}

.switch__item {
  padding: 5px 10px;
}

.switch input {
  display: none; /* Спрятанный инпут не перестаёт работать */
}

/* Применяем стили для соседа «чекнутого» инпута */
.switch input:checked + .switch__item {
  background: #00aff0;
  color: white;
}

Выпадающий список, или селект

Как и группа радиокно­пок, выпа­дю­щий спи­сок исполь­зуют для выбора объ­екта или уточ­не­ния действия.

Раз­ра­бот­чики назы­вают выпа­да­ю­щий спи­сок селек­том, потому что его вер­стают тегом select. Внутрь тега поме­щают вари­анты — option, атри­бут selected выби­рает вари­ант по умол­ча­нию. Вари­анты можно объ­еди­нять в группы — optgroup.

Внеш­ний вид селекта можно менять через ЦСС, но на внеш­ний вид вари­ан­тов повли­ять нельзя — он будет зави­сеть от опе­ра­ци­он­ной системы посе­ти­теля страницы.

<p>Доставка</p>

<select>
  <option selected>Курьером магазина</option>

  <optgroup label="Почтой России">
    <option>В отделение почты</option>
    <option>В постамат</option>
    <option>Курьером почты</option>
  </optgroup>
  
  <optgroup label="СДЭКом">
    <option>В пункт выдачи СДЭК</option>
    <option>Курьером СДЭК</option>
  </optgroup>
<select>

Доставка

Выпадающий список, или селект

Как и группа радиокнопок, выпадющий список используют для выбора объекта или уточнения действия.

Разработчики называют выпадающий список селектом, потому что его верстают тегом select. Внутрь тега помещают варианты — option, атрибут selected выбирает вариант по умолчанию. Варианты можно объединять в группы — optgroup.

Внешний вид селекта можно менять через ЦСС, но на внешний вид вариантов повлиять нельзя — он будет зависеть от операционной системы посетителя страницы.

<p>Доставка</p>

<select>
  <option selected>Курьером магазина</option>

  <optgroup label="Почтой России">
    <option>В отделение почты</option>
    <option>В постамат</option>
    <option>Курьером почты</option>
  </optgroup>
  
  <optgroup label="СДЭКом">
    <option>В пункт выдачи СДЭК</option>
    <option>Курьером СДЭК</option>
  </optgroup>
<select>

Доставка

Кнопка

Кнопки можно встре­тить в раз­ных частях стра­ницы, необя­за­тельно в фор­мах. В шапке сайта может быть кнопка пере­клю­че­ния темы оформ­ле­ния, а в кар­точке товара кнопка добав­ле­ния в кор­зину или вызова всплы­вашки с таб­ли­цей размеров.

Кнопку вер­стают тегом button, стан­дарт­ные стили тега зави­сят от бра­у­зера и опе­ра­ци­он­ной системы.

<button>Оплатить<button>

При сти­ли­за­ции не забудьте отклю­чить или изме­нить стан­дарт­ные цвета и обводку, чтобы они не кон­флик­то­вали с вашими стилями.

<button>Оплатить<button>
button {
  border: none; /* Убираем стандартную обводку */

  font-size: 20px;
  line-height: 36px;
  font-family: Bureausans, Arial, sans-serif;
  background: #2aad05;
  color: #fff;
  padding: 0 18px;
  border-radius: 3px;
}

Внутрь кнопки можно поме­стить дру­гие теги. Напри­мер, иконку:

<button>
  Подарить
  <img src="gift.svg">
<button>
button {
  border: none;
  /* Убираем стандартную обводку */

  font-size: 20px;
  line-height: 36px;
  font-family: Bureausans, Arial, sans-serif;
  background: #2aad05;
  color: #fff;
  padding: 0 18px;
  border-radius: 3px;
}

button img {
  width: 16px;
  margin-left: 5px;
}

Чтобы при­вя­зать дей­ствие к нажа­тию кнопки, нужно писать код на Ява­скрип­те. Это выхо­дит за рамки книги. Базо­вые рецепты управ­ле­ния стра­ни­цей с помо­щью кнопки можно изу­чить в видео «Ява­скрипт для не‑разработчиков».

«Яваскрипт для не‑разработчиков». Запись прямого эфира Игоря Петрова

Трюк: кнопка‑ссылка

Можно заста­вить кнопку рабо­тать как ссылка, доба­вив в в атри­бут onclick малень­кое закли­на­ние на Ява­скрип­те. Вни­ма­тель­нее с кавычками:

<button onclick="location.href='https://ya.ru'">
  Яндекс
</button>

Этот трюк при­ме­няют для быст­рого про­то­ти­пи­ро­ва­ния. Делать так на пуб­лич­ной стра­нице опасно: такую «ссылку» не пой­мут устрой­ства чте­ния с экрана и не рас­по­знают поис­ко­вые роботы. Если на стра­ницу нет ссы­лок через тег‑ссылку a, поис­ко­вики могут вовсе не уви­деть её и не доба­вить в поис­ко­вую выдачу.

Кнопка

Кнопки можно встретить в разных частях страницы, необязательно в формах. В шапке сайта может быть кнопка переключения темы оформления, а в карточке товара кнопка добавления в корзину или вызова всплывашки с таблицей размеров.

Кнопку верстают тегом button, стандартные стили тега зависят от браузера и операционной системы.

<button>Оплатить<button>

При стилизации не забудьте отключить или изменить стандартные цвета и обводку, чтобы они не конфликтовали с вашими стилями.

<button>Оплатить<button>
button {
  border: none; /* Убираем стандартную обводку */

  font-size: 20px;
  line-height: 36px;
  font-family: Bureausans, Arial, sans-serif;
  background: #2aad05;
  color: #fff;
  padding: 0 18px;
  border-radius: 3px;
}

Внутрь кнопки можно поместить другие теги. Например, иконку:

<button>
  Подарить
  <img src="gift.svg">
<button>
button {
  border: none;
  /* Убираем стандартную обводку */

  font-size: 20px;
  line-height: 36px;
  font-family: Bureausans, Arial, sans-serif;
  background: #2aad05;
  color: #fff;
  padding: 0 18px;
  border-radius: 3px;
}

button img {
  width: 16px;
  margin-left: 5px;
}

Чтобы привязать действие к нажатию кнопки, нужно писать код на Яваскрипте. Это выходит за рамки книги. Базовые рецепты управления страницей с помощью кнопки можно изучить в видео «Яваскрипт для не‑разработчиков».

«Яваскрипт для не‑разработчиков». Запись прямого эфира Игоря Петрова

Трюк: кнопка‑ссылка

Можно заставить кнопку работать как ссылка, добавив в в атрибут onclick маленькое заклинание на Яваскрипте. Внимательнее с кавычками:

<button onclick="location.href='https://ya.ru'">
  Яндекс
</button>

Этот трюк применяют для быстрого прототипирования. Делать так на публичной странице опасно: такую «ссылку» не поймут устройства чтения с экрана и не распознают поисковые роботы. Если на страницу нет ссылок через тег‑ссылку a, поисковики могут вовсе не увидеть её и не добавить в поисковую выдачу.

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

И обязательно проверьте нестандартный инпут в разных сценариях и устройствах.

Нестандартные инпуты

Когда стан­дарт­ных спо­со­бов оформ­ле­ния инпута недо­ста­точно, к сайту под­клю­чают сто­рон­ние биб­лио­теки сти­лей и скрип­тов, созда­ю­щие нестан­дарт­ные инпуты. Напри­мер, диа­па­зоны выбора дат или поля ввода по шаб­лону и с под­све­чи­ва­нием оши­бок. Нестан­дарт­ные инпуты выгля­дят заман­чиво, но имеют важ­ные нюансы.

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

Нестандартные инпуты

Когда стандартных способов оформления инпута недостаточно, к сайту подключают сторонние библиотеки стилей и скриптов, создающие нестандартные инпуты. Например, диапазоны выбора дат или поля ввода по шаблону и с подсвечиванием ошибок. Нестандартные инпуты выглядят заманчиво, но имеют важные нюансы.

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

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

И обязательно проверьте нестандартный инпут в разных сценариях и устройствах.

Подписи к полям

Под­писи к полям формы вер­стают тегом label. Лейбл умеет свя­зы­вать себя с полем, чтобы при клике по лей­блу кур­сор ста­вился в поле. Такое пове­де­ние подпи­сей облег­чает работу с фор­мой и счи­та­ется при­зна­ком хоро­шего интерфейса.

Есть два спо­соба вёрстки лейблов.

Лейбл рядом с полем

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

<label for="email">Эл. почта</label>
<input id="email" type="email">

Лейбл как обёртка

Есть трюк: лейбл авто­ма­ти­че­ски свя­зы­ва­ется с полем внутри себя, клик по любому эле­менту внутри лей­бла поста­вит кур­сор в поле. В этом слу­чае не при­дётся при­ду­мы­вать уни­каль­ные айди. Минус этого спо­соба — неко­то­рые устрой­ства чте­ния с экрана могут не понять связь лей­бла с полем ввода и неточно озву­чить вёрстку пользователю.

<label>
  <span>Эл. почта</span>
  <input type="email">
</label>

При­ят­ный бонус лей­бла как обёртки — внутри можно завер­стать не только под­пись, но и пояс­не­ния к полям или сооб­ще­ния об ошиб­ках. Клик по ним тоже будет ста­вить кур­сор в поле.

<label class="input">
  <span class="input__label">Эл. почта</span>

  <div class="input__input">
    <input type="email">

    <!-- Так же можно сверстать сообщение об ошибке. Видимостью сообщений об ошибках управляют через Яваскрипт -->
    <span class="input__caption">
      Для связи, документов и чеков
    </span>
  </div>
</label>
.input {
  display: flex;
  align-items: baseline;
}

.input__label {
  width: 25%;
}

.input__input {
  width: 75%;
}

.input__caption {
  display: block;
  margin-top: 8px;
  font-size: 12px;
}

Подписи к полям

Подписи к полям формы верстают тегом label. Лейбл умеет связывать себя с полем, чтобы при клике по лейблу курсор ставился в поле. Такое поведение подписей облегчает работу с формой и считается признаком хорошего интерфейса.

Есть два способа вёрстки лейблов.

Лейбл рядом с полем

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

<label for="email">Эл. почта</label>
<input id="email" type="email">

Для связи, документов и чеков

Лейбл как обёртка

Есть трюк: лейбл автоматически связывается с полем внутри себя, клик по любому элементу внутри лейбла поставит курсор в поле. В этом случае не придётся придумывать уникальные айди. Минус этого способа — некоторые устройства чтения с экрана могут не понять связь лейбла с полем ввода и неточно озвучить вёрстку пользователю.

<label>
  <span>Эл. почта</span>
  <input type="email">
</label>

Приятный бонус лейбла как обёртки — внутри можно заверстать не только подпись, но и пояснения к полям или сообщения об ошибках. Клик по ним тоже будет ставить курсор в поле.

<label class="input">
  <span class="input__label">Эл. почта</span>

  <div class="input__input">
    <input type="email">

    <!-- Так же можно сверстать сообщение об ошибке. Видимостью сообщений об ошибках управляют через Яваскрипт -->
    <span class="input__caption">
      Для связи, документов и чеков
    </span>
  </div>
</label>
.input {
  display: flex;
  align-items: baseline;
}

.input__label {
  width: 25%;
}

.input__input {
  width: 75%;
}

.input__caption {
  display: block;
  margin-top: 8px;
  font-size: 12px;
}

Группы полей

Когда в форме много полей, их груп­пи­руют по смыслу. Форма с груп­пами выгля­дит опрят­нее и проще для заполнения.

Для груп­пи­ровки полей удобно исполь­зо­вать тег с гово­ря­щим назва­нием fieldset.

<form>
  <fieldset>
    <label class="input">
      <span class="input__label">Получатель</span>
      <input type="text" placeholder="Андрей Петров">
    </label>
  </fieldset>


  <fieldset>
    <label class="input">
      <span class="input__label">Доставка</span>
      <select>
        <optgroup label="Почта России">
          <option>Курьером ЕМС</option>
          <option>В ближайшее отделение Почты</option>
        </optgroup>
        <optgroup label="СДЭК">
          <option selected="selected">Курьером СДЭК</option>
          <option>В пункт выдачи СДЭК</option>
        </optgroup>
      </select>
    </label>

    <label class="input">
      <span class="input__label">Город</span>
      <input type="text" placeholder="Москва">
    </label>

    <label class="input">
      <span class="input__label">Адрес</span>
      <textarea rows="2" placeholder="Ленина, 1"></textarea>
    </label>
  </fieldset>


  <fieldset>
    <label class="input">
      <span class="input__label">Оплата</span>

      <select>
        <option>Картой на сайте</option>
        <option>Картой курьеру</option>
        <option>Наличными курьеру</option>
      </select>
    </label>

    <button type="submit">Оформить заказ</button>
  </fieldset>
</form>
.input {
  display: flex;
  align-items: baseline;
}

.input__label {
  width: 25%;
}

.input__input {
  width: 75%;
}

.input__caption {
  display: block;
  margin-top: 8px;
  font-size: 12px;
}

/* Сбрасываем стандартные браузерные стили филдсета */
fieldset {
  border: none;
  margin: 0;
  padding: 0;
}

fieldset:not(:last-child) {
  margin-bottom: 27px;
}

Группировка полезна не только пользователю, но и разработчику: работать с аккуратно сгруппированными кусочками кода проще и удобнее, чем со спрошной батареей тегов

Группы полей

Когда в форме много полей, их группируют по смыслу. Форма с группами выглядит опрятнее и проще для заполнения.

Для группировки полей удобно использовать тег с говорящим названием fieldset.

<form>
  <fieldset>
    <label class="input">
      <span class="input__label">Получатель</span>
      <input type="text" placeholder="Андрей Петров">
    </label>
  </fieldset>


  <fieldset>
    <label class="input">
      <span class="input__label">Доставка</span>
      <select>
        <optgroup label="Почта России">
          <option>Курьером ЕМС</option>
          <option>В ближайшее отделение Почты</option>
        </optgroup>
        <optgroup label="СДЭК">
          <option selected="selected">Курьером СДЭК</option>
          <option>В пункт выдачи СДЭК</option>
        </optgroup>
      </select>
    </label>

    <label class="input">
      <span class="input__label">Город</span>
      <input type="text" placeholder="Москва">
    </label>

    <label class="input">
      <span class="input__label">Адрес</span>
      <textarea rows="2" placeholder="Ленина, 1"></textarea>
    </label>
  </fieldset>


  <fieldset>
    <label class="input">
      <span class="input__label">Оплата</span>

      <select>
        <option>Картой на сайте</option>
        <option>Картой курьеру</option>
        <option>Наличными курьеру</option>
      </select>
    </label>

    <button type="submit">Оформить заказ</button>
  </fieldset>
</form>

Группировка полезна не только пользователю, но и разработчику: работать с аккуратно сгруппированными кусочками кода проще и удобнее, чем со спрошной батареей тегов

.input {
  display: flex;
  align-items: baseline;
}

.input__label {
  width: 25%;
}

.input__input {
  width: 75%;
}

.input__caption {
  display: block;
  margin-top: 8px;
  font-size: 12px;
}

/* Сбрасываем стандартные браузерные стили филдсета */
fieldset {
  border: none;
  margin: 0;
  padding: 0;
}

fieldset:not(:last-child) {
  margin-bottom: 27px;
}

Отключение кнопки или инпута, дисейбл

Атри­бут disabled отклю­чает кнопку или инпут, запре­щает вза­и­мо­дей­ствие с ними. Раз­ра­бот­чики так и гово­рят: «зади­сей­б­лить кнопку».

Обычно при про­грам­ми­ро­ва­нии форм кнопки или поля дисейб­лят Ява­скрип­том. Напри­мер, запре­щают отправ­лять форму, пока запол­нены не все поля. Дисейбл часто исполь­зуют с пред­уста­нов­лен­ным через value зна­че­нием инпута.

В бра­у­зе­рах есть стан­дарт­ные стили дисей­бла, но можно сти­ли­зо­вать и по‑своему.

<input type="email" value="petrov@yandex.ru" disabled> 

<textarea disabled></textarea> 

<button disabled>Отправить</button>
:disabled {
  background: #f8f8f8;
  border: 1px solid rgba(0, 0, 0, .08);
}

Отключение кнопки или инпута, дисейбл

Атрибут disabled отключает кнопку или инпут, запрещает взаимодействие с ними. Разработчики так и говорят: «задисейблить кнопку».

Обычно при программировании форм кнопки или поля дисейблят Яваскриптом. Например, запрещают отправлять форму, пока заполнены не все поля. Дисейбл часто используют с предустановленным через value значением инпута.

В браузерах есть стандартные стили дисейбла, но можно стилизовать и по‑своему.

<input type="email" value="petrov@yandex.ru" disabled> 

<textarea disabled></textarea> 

<button disabled>Отправить</button>
:disabled {
  background: #f8f8f8;
  border: 1px solid rgba(0, 0, 0, .08);
}

Оживление формы без программирования

Чтобы свёр­стан­ная форма зара­бо­тала, нужно напи­сать код на Ява­скрип­те и настро­ить сер­вер для при­ёма и хра­не­ния оптрав­ля­е­мых дан­ных формы. Это задачи для опыт­ного разработчика.

Чтобы полу­чить рабо­чую форму без про­грам­ми­ро­ва­ния, исполь­зуют сер­висы типа Форм­кипа. Сер­висы гене­ри­руют раз­метку формы с нуж­ными полями и код для «ожив­ле­ния» этой формы. Оста­нется доба­вить код на стра­ницу и сти­ли­зо­вать форму по сво­ему усмот­ре­нию. Отправ­лен­ные из формы дан­ные при­дут в лич­ный каби­нет сер­виса, обычно допол­ни­тельно опо­ве­стив вас по почте.

Раз­ные сер­висы мало отли­ча­ются прин­ци­пи­ально. Создаём акка­унт, жмём кнопку «Новая форма», настра­и­ваем набор полей и полу­чаем код для встра­и­ва­ния на страницу.

У сер­ви­сов бывают плат­ные тарифы, уве­ли­чи­ва­ю­щие коли­че­ство и срок хра­не­ния полу­ча­е­мых дан­ных или откры­ва­ю­щие допол­ни­тель­ные настройки форм. Но для боль­шин­ства задач хва­тает и бес­плат­ных тарифов.

Формкип предлагает настроить уже свёрстанную форму или взять готовый сгенерированный код

Оживление формы без программирования

Чтобы свёрстанная форма заработала, нужно написать код на Яваскрипте и настроить сервер для приёма и хранения оптравляемых данных формы. Это задачи для опытного разработчика.

Чтобы получить рабочую форму без программирования, используют сервисы типа Формкипа. Сервисы генерируют разметку формы с нужными полями и код для «оживления» этой формы. Останется добавить код на страницу и стилизовать форму по своему усмотрению. Отправленные из формы данные придут в личный кабинет сервиса, обычно дополнительно оповестив вас по почте.

Разные сервисы мало отличаются принципиально. Создаём аккаунт, жмём кнопку «Новая форма», настраиваем набор полей и получаем код для встраивания на страницу.

У сервисов бывают платные тарифы, увеличивающие количество и срок хранения получаемых данных или открывающие дополнительные настройки форм. Но для большинства задач хватает и бесплатных тарифов.

Формкип предлагает настроить уже свёрстанную форму или взять готовый сгенерированный код

Псев­до­классы — это осо­бые слова, кото­рые добав­ляют к ука­за­телю на эле­мент в ЦСС, чтобы опи­сать стили этого эле­мента при опре­де­лён­ных усло­виях. Напри­мер, при наве­де­нии кур­сора или при клике.

Наве­де­ние кур­сора пол­но­ценно рабо­тает только на устрой­ствах с курсором.

.megaButton:hover {
  background-color: hsl(88, 80%, 48%);
}

.megaButton:active {
  background-color: hsl(88, 80%, 48%);
}

Псевдоклассы

Псевдоклассы — это особые слова, которые добавляют к указателю на элемент в ЦСС, чтобы описать стили этого элемента при определённых условиях. Например, при наведении курсора или при клике.

Наведение курсора полноценно работает только на устройствах с курсором.

.megaButton:hover {
  background-color: hsl(88, 80%, 48%);
}

.megaButton:active {
  background-color: hsl(88, 80%, 48%);
}

The first concussion cut the rocket up the side with a giant can opener. The men were thrown into space like a dozen wriggling silverfish. They were scattered into a dark sea; and the ship, in a million pieces, went on, a meteor swarm seeking a lost sun.

They fell. They fell as pebbles fall down wells. They were scattered as jackstones are scattered from a gigantic throw. And now instead of men there were only voices‑all kinds of voices, disembodied and impassioned, in varying degrees of terror and resignation.

This was true. Hollis, swinging head over heels, knew this was true. He knew it with a vague acceptance. They were parting to go their separate ways, and nothing could bring them back.

They were wearing their sealed‑tight space suits with the glass tubes over their pale faces, but they hadn't had time to lock on their force units. With them they could be small lifeboats in space, saving themselves, saving others, collecting together, finding each other until they were an island of men with some plan. But without the force units snapped to their shoulders they were meteors, senseless, each going to a separate and irrevocable fate.

Ray Bradbury. Kaleidoscope

С помо­щью псев­до­клас­сов можно сти­ли­зо­вать пер­вый и послед­ний эле­мент внутри контейнера:

<div class="article">
  <p>…</p>
  <!-- -->
  <p>…</p>
</div>
.article p:first-child {
  font-size: 150%;
  margin-bottom: 36px;
}

.article p:last-child {
  border-bottom: 1px solid;
}

С помощью псевдоклассов можно стилизовать первый и последний элемент внутри контейнера:

<div class="article">
  <p>…</p>
  <!-- -->
  <p>…</p>
</div>
.article p:first-child {
  font-size: 150%;
  margin-bottom: 36px;
}

.article p:last-child {
  border-bottom: 1px solid;
}

The first concussion cut the rocket up the side with a giant can opener. The men were thrown into space like a dozen wriggling silverfish. They were scattered into a dark sea; and the ship, in a million pieces, went on, a meteor swarm seeking a lost sun.

They fell. They fell as pebbles fall down wells. They were scattered as jackstones are scattered from a gigantic throw. And now instead of men there were only voices‑all kinds of voices, disembodied and impassioned, in varying degrees of terror and resignation.

This was true. Hollis, swinging head over heels, knew this was true. He knew it with a vague acceptance. They were parting to go their separate ways, and nothing could bring them back.

They were wearing their sealed‑tight space suits with the glass tubes over their pale faces, but they hadn't had time to lock on their force units. With them they could be small lifeboats in space, saving themselves, saving others, collecting together, finding each other until they were an island of men with some plan. But without the force units snapped to their shoulders they were meteors, senseless, each going to a separate and irrevocable fate.

Ray Bradbury. Kaleidoscope

Псев­до­клас­сом :nth-child сти­ли­зуют энные эле­менты внутри контейнера:

.item:nth-child(2),
.item:nth-child(3),
.item:nth-child(5) {
  font-family: Bureauserif, serif;
}

.item:nth-child(3) {
  color: #e6536d;
}

.item:nth-child(4) {
  font-weight: bold;
}

Клю­че­вые слова odd и even в :nth-child сти­ли­зуют нечёт­ные и чёт­ные элементы:

.item:nth-child(odd) {
  font-family: Bureauserif, serif; 
}

.item:nth-child(even) {
  font-weight: bold;
}

Не путать odd и even поможет мнемоника: в нечётном odd нечётное количество букв, а в чётном even — чётное

Фер­мер­ская и про­мыш­лен­ная еда — какая чище
Забы­тая рус­ская кухня
Ресто­раны, кото­рые откры­вают люби­тели
Как гото­вить под сушил­кой для рук
Канад­ское вино и путе­во­ди­тель по Порт­ленду
Сен­са­ци­он­ные изме­не­ния 2015 года
Два спо­соба испра­вить ошибки в сдан­ном РСВ‑1
Декла­ра­ции о налоге на при­быль, подо­зри­тель­ные для инспек­то­ров, пол­ная под­борка
Что делать, если у инспек­тора пре­тен­зии к юри­ди­че­скому адресу ком­па­нии
При­знаки под­дель­ного листка нетру­до­спо­соб­но­сти
Фермерская и промышленная еда — какая чище
Забытая русская кухня
Рестораны, которые открывают любители
Как готовить под сушилкой для рук
Канадское вино и путеводитель по Портленду
Сенсационные изменения 2015 года
Два способа исправить ошибки в сданном РСВ‑1
Декларации о налоге на прибыль, подозрительные для инспекторов, полная подборка
Что делать, если у инспектора претензии к юридическому адресу компании
Признаки поддельного листка нетрудоспособности

Псевдоклассом :nth-child стилизуют энные элементы внутри контейнера:

.item:nth-child(2),
.item:nth-child(3),
.item:nth-child(5) {
  font-family: Bureauserif, serif;
}

.item:nth-child(3) {
  color: #e6536d;
}

.item:nth-child(4) {
  font-weight: bold;
}

Ключевые слова odd и even в :nth-child стилизуют нечётные и чётные элементы:

.item:nth-child(odd) {
  font-family: Bureauserif, serif; 
}

.item:nth-child(even) {
  font-weight: bold;
}

Не путать odd и even поможет мнемоника: в нечётном odd нечётное количество букв, а в чётном even — чётное

Фокус

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

Кла­виша Tab по оче­реди пере­став­ляет фокус между эле­мен­тами управ­ле­ния в окне. Это поз­во­ляет быст­рее запол­нять формы с кла­ви­а­туры, помо­гает рабо­тать с сай­том людям с огра­ни­чен­ными возможностями.

Вид эле­мента в фокусе зави­сит от типа эле­мента и от его сти­лей. Стили фокуса зада­ются псев­до­клас­сом :focus:

/* Убираем стандартные браузерные стили фокуса */
input:focus,
button:focus {
  outline: none;
}

/* Добавляем свои стили фокуса */
input {
  /* … */
  background: transparent;
  border-bottom: 2px solid lightblue;
}

input:focus {
  border-color: #000;
}

button:focus,
button:hover {
  filter: brightness(120%);
}

/* Приятный бонус — подкрасим плейсхолдеры */
form ::placeholder {
  color: lightblue;
}

Можно пере­клю­чать фокус в обрат­ном порядке ком­би­на­цией Shift + Tab, отправ­лять формы в фокусе энте­ром, акти­ви­ро­вать ссылки и кнопки в фокусе пробелом.

Отклю­чать види­мый фокус пол­но­стью — пло­хая прак­тика. Так работа с сай­том с кла­ви­а­туры ста­нет почти невозможной.

Фокус

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

Клавиша Tab по очереди переставляет фокус между элементами управления в окне. Это позволяет быстрее заполнять формы с клавиатуры, помогает работать с сайтом людям с ограниченными возможностями.

Вид элемента в фокусе зависит от типа элемента и от его стилей. Стили фокуса задаются псевдоклассом :focus:

/* Убираем стандартные браузерные стили фокуса */
input:focus,
button:focus {
  outline: none;
}

/* Добавляем свои стили фокуса */
input {
  /* … */
  background: transparent;
  border-bottom: 2px solid lightblue;
}

input:focus {
  border-color: #000;
}

button:focus,
button:hover {
  filter: brightness(120%);
}

/* Приятный бонус — подкрасим плейсхолдеры */
form ::placeholder {
  color: lightblue;
}

Можно переключать фокус в обратном порядке комбинацией Shift + Tab, отправлять формы в фокусе энтером, активировать ссылки и кнопки в фокусе пробелом.

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

Лайфхак: псевдокласс :not

Обычно в ЦСС при­ме­няют стили к эле­менту, кото­рый соот­вет­ствует какому‑то усло­вию: явля­ется опре­де­лён­ным тегом, имеет класс, нахо­дится пер­вым, послед­ним и так далее.

Псев­до­класс :not при­ме­няет стили к эле­менту, кото­рый не соот­вет­ствует усло­вию: не явля­ется опре­де­лён­ным тегом, не имеет класса, не стоит пер­вым. Ино­гда этот псев­до­класс делает код заметно проще и нагляднее.

Напри­мер, обычно ненуж­ные отступы послед­них эле­мен­тов уби­рают в два шага: сна­чала задают отступы всем эле­мен­том, а затем обну­ляют для послед­него. Исполь­зуя :not можно напи­сать короче и нагляд­нее — ска­зать бра­у­зеру: «добавь отступ всем непо­след­ним элементам».

/* Было */
ul li { margin-bottom: 9px; }
ul li:last-child { margin-bottom: 0; }

/* Стало */
ul li:not(:last-child) { margin-bottom: 9px; }

/* Ещё примеры: всё в .content, кроме лида */
.content :not(.lead) { … }

/* Все в article, кроме p */
article *:not(p) { … }

Вложить один :not в другой не получится, зато можно сделать забавный в своей бессмысленности селектор :not(*), который найдёт любой элемент, являющийся не любым, то есть не найдёт ничего

Лайфхак: псевдокласс :not

Обычно в ЦСС применяют стили к элементу, который соответствует какому‑то условию: является определённым тегом, имеет класс, находится первым, последним и так далее.

Псевдокласс :not применяет стили к элементу, который не соответствует условию: не является определённым тегом, не имеет класса, не стоит первым. Иногда этот псевдокласс делает код заметно проще и нагляднее.

Например, обычно ненужные отступы последних элементов убирают в два шага: сначала задают отступы всем элементом, а затем обнуляют для последнего. Используя :not можно написать короче и нагляднее — сказать браузеру: «добавь отступ всем непоследним элементам».

/* Было */
ul li { margin-bottom: 9px; }
ul li:last-child { margin-bottom: 0; }

/* Стало */
ul li:not(:last-child) { margin-bottom: 9px; }

/* Ещё примеры: всё в .content, кроме лида */
.content :not(.lead) { … }

/* Все в article, кроме p */
article *:not(p) { … }

Вложить один :not в другой не получится, зато можно сделать забавный в своей бессмысленности селектор :not(*), который найдёт любой элемент, являющийся не любым, то есть не найдёт ничего

Псевдо­элементы ::after и ::before

Одна из кру­тей­ших воз­мож­но­стей ЦСС — добав­ле­ние содер­жи­мого в начало и в конец почти любого эле­мента с помо­щью слов ::before и ::after.

<blockquote>
  Волк слабее льва и тигра, но в цирке он не выступает
</blockquote>
blockquote::before {
  content: '«';
}

blockquote::after {
  content: '»';
}

Слова ::before и ::after добав­ляют в вёрстку псев­до­эле­менты — «вир­ту­аль­ные» кусочки раз­метки, кото­рых нет в исход­ной ХТМЛ‑раз­метке. Из‑за этого содер­жи­мое псев­до­эле­мен­тов не учи­ты­ва­ется поис­ко­ви­ками, плохо рас­по­зна­ётся устрой­ствами чте­ния с экрана, не выде­ля­ется и не копи­ру­ется. Поэтому не реко­мен­дуем вер­стать псев­до­эле­мен­тами важ­ное содер­жа­ние сайта.

С дру­гой сто­роны, если вам нужно спря­тать текст от поис­ко­ви­ков или поме­шать выде­ле­нию и копи­ро­ва­нию, то один из спо­со­бов — поме­стить текст в псевдоэлементы.

Псев­до­эле­менты нельзя доба­вить к эле­мен­там с неза­кры­ва­ю­щи­мися тегами, вроде img. Но можно обер­нуть img в div и доба­вить псев­до­эле­менты уже к нему.

Волк сла­бее льва и тигра, но в цирке он не выступает

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

Волк слабее льва и тигра, но в цирке он не выступает

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

Псевдо­элементы ::after и ::before

Одна из крутейших возможностей ЦСС — добавление содержимого в начало и в конец почти любого элемента с помощью слов ::before и ::after.

<blockquote>
  Волк слабее льва и тигра, но в цирке он не выступает
</blockquote>
blockquote::before {
  content: '«';
}

blockquote::after {
  content: '»';
}

Слова ::before и ::after добавляют в вёрстку псевдоэлементы — «виртуальные» кусочки разметки, которых нет в исходной ХТМЛ‑разметке. Из‑за этого содержимое псевдоэлементов не учитывается поисковиками, плохо распознаётся устройствами чтения с экрана, не выделяется и не копируется. Поэтому не рекомендуем верстать псевдоэлементами важное содержание сайта.

С другой стороны, если вам нужно спрятать текст от поисковиков или помешать выделению и копированию, то один из способов — поместить текст в псевдоэлементы.

Псевдоэлементы нельзя добавить к элементам с незакрывающимися тегами, вроде img. Но можно обернуть img в div и добавить псевдоэлементы уже к нему.

Никола Пуссен. Рождение Венеры. 1636

Псев­до­эле­менты можно сти­ли­зо­вать так же, как обыч­ные эле­менты, а зна­чит, воз­мож­но­сти при­ме­не­ния псев­до­эле­мен­тов огра­ни­чены только воз­мож­но­стями ЦСС и вашей фантазией.

Сде­лаем класс для затем­не­ния и заблю­ри­ва­ния содер­жи­мого кон­тей­нера. Свой­ство position мы подроб­нее раз­бе­рём дальше в книге.

<div class="adult">
  <img src="the-birth-of-venus.jpg">
</div>
.adult {
  position: relative; /* relative даст поместить другой элемент поверх этого */
}

.adult::after {
  content: ''; /* Обязательно указывать content для ::before и ::after, даже если нужен псевдоэлемент без содержимого. Если не указать content, псевдоэлемент не появится */
  display: block; /* По умолчанию ::before и ::after инлайновые, нам нужен блочный элемент для установки размеров */
  position: absolute;
  inset: 0;
  width: 100%; /* Растягиваем элемент по ширине и высоте */
  height: 100%;
  z-index: 2; /* Размещаем псевдоэлемент поверх .adult */
  background: rgba(0, 0, 0, 0.15);
  backdrop-filter: blur(5px);
}

Поскольку у эле­мента может быть два псев­до­эле­мента, исполь­зуем вто­рой, чтобы доба­вить над­пись поверх блюра:

.adult {
  …
}

.adult::after {
  …
}

.adult:before {
  content: '16+';
  font-size: 24px;
  color: #fff;
  text-shadow: 1px 1px 20px rgb(0 0 0 0.5);
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 3; /* Размещаем поверх ::after */
  transform: translate(-50%, -50%); /* Ставим элемент по центру родителя */
}

Никола Пуссен. Рождение Венеры. 1636

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

Сделаем класс для затемнения и заблюривания содержимого контейнера. Свойство position мы подробнее разберём дальше в книге.

<div class="adult">
  <img src="the-birth-of-venus.jpg">
</div>
.adult {
  position: relative; /* relative даст поместить другой элемент поверх этого */
}

.adult::after {
  content: ''; /* Обязательно указывать content для ::before и ::after, даже если нужен псевдоэлемент без содержимого. Если не указать content, псевдоэлемент не появится */
  display: block; /* По умолчанию ::before и ::after инлайновые, нам нужен блочный элемент для установки размеров */
  position: absolute;
  inset: 0;
  width: 100%; /* Растягиваем элемент по ширине и высоте */
  height: 100%;
  z-index: 2; /* Размещаем псевдоэлемент поверх .adult */
  background: rgba(0, 0, 0, 0.15);
  backdrop-filter: blur(5px);
}

Поскольку у элемента может быть два псевдоэлемента, используем второй, чтобы добавить надпись поверх блюра:

.adult {
  …
}

.adult::after {
  …
}

.adult:before {
  content: '16+';
  font-size: 24px;
  color: #fff;
  text-shadow: 1px 1px 20px rgb(0 0 0 0.5);
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 3; /* Размещаем поверх ::after */
  transform: translate(-50%, -50%); /* Ставим элемент по центру родителя */
}

Трюк: увеличение области клика с помощью псевдоэлемента

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

Псев­до­эле­мент с абсо­лют­ным пози­ци­о­ни­ро­ва­нием и отри­ца­тель­ными вытяж­ками уве­ли­чит область клика эле­мента на раз­мер отри­ца­тель­ной вытяжки. Абсо­лют­ное пози­ци­о­ни­ро­ва­ние мы подроб­нее раз­бе­рём дальше в книге.

В при­мере область клика ссы­лок под­кра­шена для наглядности.

.extClickArea {
  position: relative;
}

.extClickArea::after {
  content: '';
  position: absolute;
  
  /* Вытягиваем псевдоэлемент на −0,3em за границы родителя — самого элемента */
  inset: -0.3em;
}

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

Трюк: увеличение области клика с помощью псевдоэлемента

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

Псевдоэлемент с абсолютным позиционированием и отрицательными вытяжками увеличит область клика элемента на размер отрицательной вытяжки. Абсолютное позиционирование мы подробнее разберём дальше в книге.

В примере область клика ссылок подкрашена для наглядности.

.extClickArea {
  position: relative;
}

.extClickArea::after {
  content: '';
  position: absolute;
  
  /* Вытягиваем псевдоэлемент на −0,3em за границы родителя — самого элемента */
  inset: -0.3em;
}

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

Тест по разделу «Модули»

Для чего мы советуем перечислять несколько шрифтов в font-family?

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

Если на компьютере не установлен шрифт, если он не загрузился или в нём нет нужной буквы, браузер попробует следующий шрифт из списка

Как работают ключевые слова serif или sans-serif в font-family?

Если браузер не сможет использовать указанные до ключевых слов шрифты, то использует системный шрифт соответствующего типа: с засечками или без
Это модификаторы шрифта: если указан serif, браузер включит только строчные; если указан sans-serif, выключит их
Это заклинание для сглаживания шрифтов в Гугль Хроме до десятой версии
Если их не указать, браузер будет работать в режиме совместимости со старыми версиями Интернет Эксплорера и Нетскейп Навигатора

Ключевое слово в конце списка описывает тип шрифта: serif — с засечками, sans‑serif — без засечек. Если браузер доберётся до него, то возьмёт системный шрифт соответствующего семейства

По какому признаку шрифты в этой книге называют веб‑безопасными?

Шрифты, установленные в большинстве операционных систем
Шрифты без уязвимостей и вирусов
Рекомендованные дизайнерами варианты шрифтовых пар, которые хорошо сочетаются
Шрифты, которые хорошо распознают люди с проблемами зрения

Веб‑безопасными называют шрифты, входящие в стандартный набор операционной системы

Выберите верные утверждения о назначении ЦСС‑свойств.

font-size — кегль
font-size — ширина текстовой колонки
line-height — высота символов
line-height — интерлиньяж
font-weight — кегль
font-weight — максимально допустимый размер файла шрифта
font-weight — «жирность» шрифта
font-style — подчёркивание, зачёркивание, надчёркивание
font-style — начертание шрифта, курсив и наклон
text-decoration — особенности шрифта, лигатуры и сглаживание
text-decoration — подчёркивание, зачёркивание, надчёркивание

font‑size и line‑height управляют кеглем и интерлиньяжем, font‑style управляет начертанием, font‑weight задаёт «жирность» шрифта, а text‑decoration добавляет подчёркивание, зачёркивание или надчёркивание

Какому элементу мы советуем задавать основной шрифт страницы и почему?

Всем элементам страницы для надёжности, принцип «ковровая бомбардировка»
Только текстовым элементам, чтобы не стилизовать лишнего, принцип наследования в ЦСС
Тегу body, чтобы стили не пришлось повторять для всего подряд, принцип наследования в ЦСС
Обёрткам article и main, чтобы стили наследовались для всех статей и текстов, принцип «семантическое ядро»

Наследование стилей позволяет писать меньше кода, избегать повторов стилей, последовательно стилизовать страницу от общего к частному

Чем управляет свойство font-variant?

Капителью
Кеглем
Лигатурами
Интерлиньяжем

Свойство font‑variant включает лигатуры и капитель

Для чего мы рекомендуем использовать атрибут alt?

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

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

Ответьте на все вопросы теста,
чтобы узнать результат.
Результат
↺ Пересдать

Тест по разделу «Модули»

Для чего мы советуем перечислять несколько шрифтов в font-family?

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

Если на компьютере не установлен шрифт, если он не загрузился или в нём нет нужной буквы, браузер попробует следующий шрифт из списка

Как работают ключевые слова serif или sans-serif в font-family?

Если браузер не сможет использовать указанные до ключевых слов шрифты, то использует системный шрифт соответствующего типа: с засечками или без
Это модификаторы шрифта: если указан serif, браузер включит только строчные; если указан sans-serif, выключит их
Это заклинание для сглаживания шрифтов в Гугль Хроме до десятой версии
Если их не указать, браузер будет работать в режиме совместимости со старыми версиями Интернет Эксплорера и Нетскейп Навигатора

Ключевое слово в конце списка описывает тип шрифта: serif — с засечками, sans‑serif — без засечек. Если браузер доберётся до него, то возьмёт системный шрифт соответствующего семейства

По какому признаку шрифты в этой книге называют веб‑безопасными?

Шрифты, установленные в большинстве операционных систем
Шрифты без уязвимостей и вирусов
Рекомендованные дизайнерами варианты шрифтовых пар, которые хорошо сочетаются
Шрифты, которые хорошо распознают люди с проблемами зрения

Веб‑безопасными называют шрифты, входящие в стандартный набор операционной системы

Выберите верные утверждения о назначении ЦСС‑свойств.

font-size — кегль
font-size — ширина текстовой колонки
line-height — высота символов
line-height — интерлиньяж
font-weight — кегль
font-weight — максимально допустимый размер файла шрифта
font-weight — «жирность» шрифта
font-style — подчёркивание, зачёркивание, надчёркивание
font-style — начертание шрифта, курсив и наклон
text-decoration — особенности шрифта, лигатуры и сглаживание
text-decoration — подчёркивание, зачёркивание, надчёркивание

font‑size и line‑height управляют кеглем и интерлиньяжем, font‑style управляет начертанием, font‑weight задаёт «жирность» шрифта, а text‑decoration добавляет подчёркивание, зачёркивание или надчёркивание

Какому элементу мы советуем задавать основной шрифт страницы и почему?

Всем элементам страницы для надёжности, принцип «ковровая бомбардировка»
Только текстовым элементам, чтобы не стилизовать лишнего, принцип наследования в ЦСС
Тегу body, чтобы стили не пришлось повторять для всего подряд, принцип наследования в ЦСС
Обёрткам article и main, чтобы стили наследовались для всех статей и текстов, принцип «семантическое ядро»

Наследование стилей позволяет писать меньше кода, избегать повторов стилей, последовательно стилизовать страницу от общего к частному

Чем управляет свойство font-variant?

Капителью
Кеглем
Лигатурами
Интерлиньяжем

Свойство font‑variant включает лигатуры и капитель

Для чего мы рекомендуем использовать атрибут alt?

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

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

Ответьте на все вопросы теста, чтобы узнать результат.
Результат
↺ Пересдать

2
Расстановка

2
Расстановка

Позициони­ро­вание

Есть несколько спо­со­бов раз­ло­жить эле­менты по стра­нице: пози­ци­о­ни­ро­ва­ние, фло­аты, таб­лицы, флек­с­боксы и гриды. Мы посте­пенно раз­бе­рём их все, а нач­нём с самого про­стого — пози­ци­о­ни­ро­ва­ния свой­ством position.

По умол­ча­нию блоки выстра­и­ва­ются друг под дру­гом. С помо­щью position можно изме­нить это пове­де­ние и раз­ме­стить эле­мент в любом месте стра­ницы, задав ему коор­ди­наты свой­ствами top, right, left и bottom. Есть пять зна­че­ний свойства:

Static

Это зна­че­ние по умол­ча­нию, отсут­ствие какого‑либо пози­ци­о­ни­ро­ва­ния. Эле­менты про­сто выстра­и­ва­ются друг под другом.

Relative

Эле­мент с отно­си­тель­ным пози­ци­о­ни­ро­ва­нием сдви­га­ется со сво­его места, не влияя на поло­же­ние сосед­них элементов.

position: relative;
left: 50px;
top: 80px;

Absolute

Эле­мент с абсо­лют­ным пози­ци­о­ни­ро­ва­нием рас­по­ла­га­ется по задан­ным коор­ди­на­там, а из того места, где он был, удаляется.

position: absolute;
left: 50px;
top: 80px;

По умол­ча­нию эти коор­ди­наты отсчи­ты­ва­ются от окна. Но если у эле­мента есть роди­тель с position: relative или absolute, то коор­ди­наты будут отсчи­ты­ваться уже от него.

Fixed и sticky

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

position: sticky;
top: 18px;

Чаще всего исполь­зу­ется для «зали­па­ю­щих» эле­мен­тов. Напри­мер, с помо­щью position: sticky зали­пают иконки закладки и поиска в нашей книге. Ана­ло­гично рабо­тает и этот раз­во­рот: стра­ница с при­ме­ром зали­пает с position: sticky, а дру­гая прокручивается.

Позициони­ро­вание

Есть несколько способов разложить элементы по странице: позиционирование, флоаты, таблицы, флексбоксы и гриды. Мы постепенно разберём их все, а начнём с самого простого — позиционирования свойством position.

По умолчанию блоки выстраиваются друг под другом. С помощью position можно изменить это поведение и разместить элемент в любом месте страницы, задав ему координаты свойствами top, right, left и bottom. Есть пять значений свойства:

Static

Это значение по умолчанию, отсутствие какого‑либо позиционирования. Элементы просто выстраиваются друг под другом.

Relative

Элемент с относительным позиционированием сдвигается со своего места, не влияя на положение соседних элементов.

position: relative;
left: 50px;
top: 80px;

Absolute

Элемент с абсолютным позиционированием располагается по заданным координатам, а из того места, где он был, удаляется.

position: absolute;
left: 50px;
top: 80px;

По умолчанию эти координаты отсчитываются от окна. Но если у элемента есть родитель с position: relative или absolute, то координаты будут отсчитываться уже от него.

Fixed и sticky

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

position: sticky;
top: 18px;

Чаще всего используется для «залипающих» элементов. Например, с помощью position: sticky залипают иконки закладки и поиска в нашей книге. Аналогично работает и этот разворот: страница с примером залипает с position: sticky, а другая прокручивается.

position: relative

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

.dots { position: relative }
.dot { position: absolute } 

.top.left { top: 7px; left: 7px }
.top.right { top: 7px; right: 7px }
.bottom.left { bottom: 7px; left: 7px }
.bottom.right { bottom: 7px; right: 7px }

Важ­ный момент: абсо­лютно спо­зи­ци­о­ни­ро­ван­ный эле­мент будет отсчи­ты­вать свои коор­ди­наты от бли­жай­шего роди­теля с position: relative

.dots { position: relative }

.dot {
  position: absolute;
  top: 0;
  right: 0;
}

position: relative

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

.dots { position: relative }
.dot { position: absolute } 

.top.left { top: 7px; left: 7px }
.top.right { top: 7px; right: 7px }
.bottom.left { bottom: 7px; left: 7px }
.bottom.right { bottom: 7px; right: 7px }

Важный момент: абсолютно спозиционированный элемент будет отсчитывать свои координаты от ближайшего родителя с position: relative

.dots { position: relative }

.dot {
  position: absolute;
  top: 0;
  right: 0;
}

position: absolute

Абсо­лют­ное пози­ци­о­ни­ро­ва­ние исполь­зуют, когда нужно что‑то выта­щить из потока и пере­ста­вить в дру­гое место: поста­вить в угол, на поля или по цен­тру. Кроме того, абсо­лют­ное пози­ци­о­ни­ро­ва­ние часто исполь­зуют в паре с псевдо‑эле­мен­тами: напри­мер, для добав­ле­ния линеек, мар­ке­ров или подписей.

Так в при­мере выше мы исполь­зуем псевдо‑эле­мент ::before, чтобы доба­вить верх­нюю линейку к меню, и ::after, чтобы доба­вить ниж­нюю линейку и звёз­дочку к пункту меню.

Также обра­тите вни­ма­ние на отри­ца­тель­ные коор­ди­наты: с помо­щью абсо­лют­ного пози­ци­о­ни­ро­ва­ния эле­менты можно рас­тя­ги­вать и выно­сить за пре­делы контейнера.

nav {
  /* Абсолютно спозиционированные линейки
    будут отсчитывать свои координаты от меню */
  position: relative;
  /* Добавляем поля сверху-снизу */
  padding: 0 8px;
}

nav::before,
nav::after {
  /* Используем псевдо-элементы в качестве линеек:
    даём им высоту и фон */
  content: '';
  height: 1px;
  background: rgba(0, 0, 0, .07);
  /* Растягиваем линейки от края до края без полей */
  position: absolute;
  left: -8px;
  right: -8px;
}

nav::before {
  /* Первую линейку ставим над меню */
  top: 0;
}

nav::after {
  /* Вторую линейку ставим под меню */
  bottom: -4px;
}

nav li:nth-child(3) {
  /* У «Домов» будет свой особенный значок */
  position: relative;
}

nav li:nth-child(3)::after {
  /* Добавляем звёздочку перед «Домами» */
  content: '★';
  color: red;
  /* Ставим её перед текстом */
  position: absolute;
  right: 100%;
  margin-right: 1px;
}

position: absolute

Абсолютное позиционирование используют, когда нужно что‑то вытащить из потока и переставить в другое место: поставить в угол, на поля или по центру. Кроме того, абсолютное позиционирование часто используют в паре с псевдо‑элементами: например, для добавления линеек, маркеров или подписей.

Так в примере выше мы используем псевдо‑элемент ::before, чтобы добавить верхнюю линейку к меню, и ::after, чтобы добавить нижнюю линейку и звёздочку к пункту меню.

Также обратите внимание на отрицательные координаты: с помощью абсолютного позиционирования элементы можно растягивать и выносить за пределы контейнера.

nav {
  /* Абсолютно спозиционированные линейки
    будут отсчитывать свои координаты от меню */
  position: relative;
  /* Добавляем поля сверху-снизу */
  padding: 0 8px;
}

nav::before,
nav::after {
  /* Используем псевдо-элементы в качестве линеек:
    даём им высоту и фон */
  content: '';
  height: 1px;
  background: rgba(0, 0, 0, .07);
  /* Растягиваем линейки от края до края без полей */
  position: absolute;
  left: -8px;
  right: -8px;
}

nav::before {
  /* Первую линейку ставим над меню */
  top: 0;
}

nav::after {
  /* Вторую линейку ставим под меню */
  bottom: -4px;
}

nav li:nth-child(3) {
  /* У «Домов» будет свой особенный значок */
  position: relative;
}

nav li:nth-child(3)::after {
  /* Добавляем звёздочку перед «Домами» */
  content: '★';
  color: red;
  /* Ставим её перед текстом */
  position: absolute;
  right: 100%;
  margin-right: 1px;
}

position: fixed и sticky

Фик­си­ро­ван­ное пози­ци­о­ни­ро­ва­ние исполь­зуют для штук, кото­рые все­гда должны быть в обла­сти види­мо­сти: кнопка «Зака­зать дизайн», диа­лог в Чатре или сооб­ще­ние об исполь­зо­ва­нии кук на сайте.

.fixed {
  position: fixed;
  left: 20px;
  bottom: 20px;
}

Зали­па­ю­щее пози­ци­о­ни­ро­ва­ние исполь­зуют, когда нужно, чтобы эле­мент стоял на месте, пока до него не докру­тят, затем залип, а при обрат­ной про­крутке — отлип.

.sticky {
  position: sticky;
  top: 20px;
}

position: fixed и sticky

Фиксированное позиционирование используют для штук, которые всегда должны быть в области видимости: кнопка «Заказать дизайн», диалог в Чатре или сообщение об использовании кук на сайте.

.fixed {
  position: fixed;
  left: 20px;
  bottom: 20px;
}

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

.sticky {
  position: sticky;
  top: 20px;
}
Кто такой? Чем зна­ме­нит?Я вели­кий ком­би­на­тор, идей­ный борец за денеж­ные знаки.Кроме того, я знаю четы­ре­ста срав­ни­тельно чест­ных спо­соба отъ­ёма денег.АферыВ городе Арба­тове выдал себя за сына лей­те­нанта Шмидта и полу­чил от пред­се­да­теля гор­ис­пол­кома неболь­шую мате­ри­аль­ную помощь.

Флоаты

С помо­щью фло­атов эле­мент можно при­жать либо к левому, либо к пра­вому краю роди­теля. Сле­ду­ю­щие за ним эле­менты под­тя­ги­ва­ются вверх, пыта­ясь занять его место, или обте­кают его.

Фло­аты исполь­зуют редко, чаще всего, чтобы задать кар­тинке обте­ка­ние текстом:

<section>
  <img src="ostap.jpg">

  <h3>Кто такой? Чем знаменит?</h3>
  <p>
     Я великий комбинатор,
     идейный борец за денежные знаки.
  </p>
  <p>
     Кроме того, я знаю четыреста сравнительно честных
     способа отъёма денег.
  </p>
</section>
img {
  /* Прижимаем картинку вправо */
  float: right;
  /* Фиксируем высоту картинки */
  height: 8em;
  width: auto;
  /* Добавляем отступ слева,
    чтобы текст не влипал в иллюстрацию */
  margin-left: 1em;
}

Давным‑давно разработчики «хакали» ЦСС, создавая колоночную вёрстку на флоатах и маржинах:

Кто такой? Чем знаменит?Я великий комбинатор, идейный борец за денежные знаки.Кроме того, я знаю четыреста сравнительно честных способа отъёма денег.АферыВ городе Арбатове выдал себя за сына лейтенанта Шмидта и получил от председателя горисполкома небольшую материальную помощь.

Флоаты

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

Флоаты используют редко, чаще всего, чтобы задать картинке обтекание текстом:

<section>
  <img src="ostap.jpg">

  <h3>Кто такой? Чем знаменит?</h3>
  <p>
     Я великий комбинатор,
     идейный борец за денежные знаки.
  </p>
  <p>
     Кроме того, я знаю четыреста сравнительно честных
     способа отъёма денег.
  </p>
</section>
img {
  /* Прижимаем картинку вправо */
  float: right;
  /* Фиксируем высоту картинки */
  height: 8em;
  width: auto;
  /* Добавляем отступ слева,
    чтобы текст не влипал в иллюстрацию */
  margin-left: 1em;
}

Давным‑давно разработчики «хакали» ЦСС, создавая колоночную вёрстку на флоатах и маржинах:

Флексбоксы

Флек­с­бокс — это набор ЦСС‑свойств для гиб­кой рас­кладки эле­мен­тов на стра­нице или в отдель­ном блоке.

Чтобы вклю­чить флек­с­бокс, нужно задать эле­менту display: flex. Тогда эле­мент ста­нет флекс‑кон­тей­не­ром, а все вло­жен­ные в него эле­менты ста­нут флекс‑элементами.

Отно­си­тесь к флекс‑кон­тей­неру, как к пря­мо­уголь­ному фор­мату, внутри кото­рого можно рас­по­ло­жить несколько эле­мен­тов по стро­кам или колон­кам, выров­няв их по одной из сто­рон или рас­пре­де­лив по формату.

Нач­нём с разметки:

<div class="columns">
  <div>Первая</div>
  <div>Вторая</div>
  <div>Третья</div>
</div>

Доба­вим колон­кам цвет и отступы для наглядности:

/* Составной селектор > * выделяет
   любые элементы (*), находящиеся
   непосредственно в .columns (>) */
.columns > * {
  background: #e5f5ff;
  padding: 9px;
}

Вклю­чим флексбокс:

.columns {
  display: flex;
}

Рав­но­мерно рас­пре­де­лим колонки, отжав первую и послед­нюю к краям:

.columns {
  display: flex;
  justify-content: space-between;
}
Пер­вая
Вто­рая
Тре­тья
Пер­вая
Вто­рая
Тре­тья
Пер­вая
Вто­рая
Тре­тья
Пер­вая
Вто­рая
Тре­тья
Первая
Вторая
Третья
Первая
Вторая
Третья
Первая
Вторая
Третья
Первая
Вторая
Третья

Флексбоксы

Флексбокс — это набор ЦСС‑свойств для гибкой раскладки элементов на странице или в отдельном блоке.

Чтобы включить флексбокс, нужно задать элементу display: flex. Тогда элемент станет флекс‑контейнером, а все вложенные в него элементы станут флекс‑элементами.

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

Начнём с разметки:

<div class="columns">
  <div>Первая</div>
  <div>Вторая</div>
  <div>Третья</div>
</div>

Добавим колонкам цвет и отступы для наглядности:

/* Составной селектор > * выделяет
   любые элементы (*), находящиеся
   непосредственно в .columns (>) */
.columns > * {
  background: #e5f5ff;
  padding: 9px;
}

Включим флексбокс:

.columns {
  display: flex;
}

Равномерно распределим колонки, отжав первую и последнюю к краям:

.columns {
  display: flex;
  justify-content: space-between;
}

🕑

Глав­ная ось →

Попе­реч­ная ось →

Серая область — флекс‑контейнер, «кубики» внутри — флекс‑элементы

Клю­че­вая идея рас­кладки флек­с­бок­сами — вве­де­ние глав­ной и попе­реч­ной осей, по кото­рым и будут рас­кла­ды­ваться флекс‑элементы.

Ключевая идея раскладки флексбоксами — введение главной и поперечной осей, по которым и будут раскладываться флекс‑элементы.

🕑

Главная ось →

Поперечная ось →

Серая область — флекс‑контейнер, «кубики» внутри — флекс‑элементы

flex‑direction: row

🕑

flex‑direction: row‑reverse

🕑

flex‑direction: column

🕑

flex‑direction: column‑reverse

🕑

Главная ось

За направ­ле­ние глав­ной оси отве­чает flex-direction. Если эле­менты нужно раз­ло­жить слева направо, исполь­зуем flex-direction: row; справа налево — row-reverse. Если сверху вниз, исполь­зуем flex-direction: column; снизу вверх — column-reverse

Главная ось

За направление главной оси отвечает flex-direction. Если элементы нужно разложить слева направо, используем flex-direction: row; справа налево — row-reverse. Если сверху вниз, используем flex-direction: column; снизу вверх — column-reverse

flex‑direction: row

🕑

flex‑direction: column

🕑

flex‑direction: row‑reverse

🕑

flex‑direction: column‑reverse

🕑

justify‑content: flex‑start

🕑

justify‑content: center

🕑

justify‑content: flex‑end

🕑

За вырав­ни­ва­ние эле­мен­тов на глав­ной оси отве­чает свой­ство justify-content. Если эле­менты нужно при­жать к началу оси, исполь­зуем justify-content: flex-start; если к концу, то justify-content: flex-end; если к цен­тру, то justify-content: center

За выравнивание элементов на главной оси отвечает свойство justify-content. Если элементы нужно прижать к началу оси, используем justify-content: flex-start; если к концу, то justify-content: flex-end; если к центру, то justify-content: center

justify‑content: flex‑start

🕑

justify‑content: center

🕑

justify‑content: flex‑end

🕑

justify‑content: space‑between

🕑

justify‑content: space‑around

🕑

justify‑content: space‑evenly

🕑

Чтобы отжать пер­вый и послед­ний эле­мент к краям, а остав­ше­еся место поде­лить, исполь­зуем justify-content: space-between. Чтобы рав­но­мерно поде­лить место между всеми эле­мен­тами, исполь­зуем justify-content: space-around или justify-content: space-evenly

space-around рав­но­мерно рас­пре­де­ляет эле­менты по ширине таким обра­зом, чтобы рас­сто­я­ние до краёв блока рав­ня­лось поло­вине рас­сто­я­ния между эле­мен­тами. space-evenly рас­пре­де­ляет эле­менты так, чтобы все рас­сто­я­ния были одинаковы.

Чтобы отжать первый и последний элемент к краям, а оставшееся место поделить, используем justify-content: space-between. Чтобы равномерно поделить место между всеми элементами, используем justify-content: space-around или justify-content: space-evenly

space-around равномерно распределяет элементы по ширине таким образом, чтобы расстояние до краёв блока равнялось половине расстояния между элементами. space-evenly распределяет элементы так, чтобы все расстояния были одинаковы.

justify‑content: space‑between

🕑

justify‑content: space‑around

🕑

justify‑content: space‑evenly

🕑

align‑items: stretch

🕑

По умолчанию элементы растягиваются на всю поперечную ось. Это align‑items: stretch

align‑items: baseline

🕑

Если элементы нужно выровнять по базовой, используем align‑items: baseline

align‑items: flex‑start

🕑

Чтобы прижать элементы на поперечной оси к началу, используем align‑items: flex‑start

align‑items: center

🕑

Чтобы прижать к центру, используем align‑items: center

align‑items: flex‑end

🕑

Чтобы прижать к концу, используем align‑items: flex‑end

Поперечная ось

Попе­реч­ная ось идёт пер­пен­ди­ку­лярно глав­ной. Соот­вет­ственно, если глав­ная ось идёт по гори­зон­тали, то попе­реч­ная прой­дёт по вер­ти­кали. И нао­бо­рот: если глав­ная ось идёт по вер­ти­кали, попе­реч­ная прой­дёт по горизонтали.

За вырав­ни­ва­ние эле­мен­тов на попе­реч­ной оси отве­чает свой­ство align-items

Поперечная ось

Поперечная ось идёт перпендикулярно главной. Соответственно, если главная ось идёт по горизонтали, то поперечная пройдёт по вертикали. И наоборот: если главная ось идёт по вертикали, поперечная пройдёт по горизонтали.

За выравнивание элементов на поперечной оси отвечает свойство align-items

align‑items: stretch

🕑

По умолчанию элементы растягиваются на всю поперечную ось. Это align‑items: stretch

align‑items: baseline

🕑

Если элементы нужно выровнять по базовой, используем align‑items: baseline

align‑items: flex‑start

🕑

Чтобы прижать элементы на поперечной оси к началу, используем align‑items: flex‑start

align‑items: center

🕑

Чтобы прижать к центру, используем align‑items: center

align‑items: flex‑end

🕑

Чтобы прижать к концу, используем align‑items: flex‑end

align‑self: flex‑start

🕑

align‑self: center

🕑

align‑self: flex‑end

🕑

Все зна­че­ния align-items, что мы рас­смот­рели выше, при­ме­ня­ются ко флекс‑кон­тей­неру и вли­яют на все эле­менты внутри него. Если отдель­ные эле­менты нужно выров­нять по‑осо­бен­ному на попе­реч­ной оси, исполь­зуют align-self в самих элементах.

Все значения align-items, что мы рассмотрели выше, применяются ко флекс‑контейнеру и влияют на все элементы внутри него. Если отдельные элементы нужно выровнять по‑особенному на поперечной оси, используют align-self в самих элементах.

align‑self: flex‑start

🕑

align‑self: center

🕑

align‑self: flex‑end

🕑

В вырав­ни­ва­нии флекс‑эле­мен­тов есть трюк: если задать эле­менту мар­жин auto, он при­жмётся к про­ти­во­по­лож­ной стороне.

1
2
3
4
.module > :nth-child(3) {
  margin-left: auto;
  /* Сдвинет третий и последующие */
}

.module > :nth-child(4) {
  margin-bottom: auto;
}

Чаще всего авто­ма­ти­че­ские мар­жины исполь­зуют в моду­лях, чтобы «оттеснить» часть эле­мен­тов к краю или рас­ста­вить их по углам.

Управ­ле­ние про­ек­том
304 совета
.tag {
  display: flex;
}

.tag__caption {
  margin-top: auto;
  margin-left: auto;
}

В выравнивании флекс‑элементов есть трюк: если задать элементу маржин auto, он прижмётся к противоположной стороне.

1
2
3
4
.module > :nth-child(3) {
  margin-left: auto;
  /* Сдвинет третий и последующие */
}

.module > :nth-child(4) {
  margin-bottom: auto;
}

Чаще всего автоматические маржины используют в модулях, чтобы «оттеснить» часть элементов к краю или расставить их по углам.

Управление проектом
304 совета
.tag {
  display: flex;
}

.tag__caption {
  margin-top: auto;
  margin-left: auto;
}

Упражнение: применение флексбокса

Пере­та­щите ЦСС‑свой­ства в селек­торы, чтобы пункты меню встали в ряд, а послед­ний пункт меню при­жался к пра­вому краю.


  
display: flex;
margin-left: auto;
nav {
}
nav li:last-child {
}

Вёрстка

Дизайн

Вёрстка

Дизайн

Упражнение: применение флексбокса

Перетащите ЦСС‑свойства в селекторы, чтобы пункты меню встали в ряд, а последний пункт меню прижался к правому краю.


  
display: flex;
margin-left: auto;
nav {
}
nav li:last-child {
}

Ком­би­на­цию рас­кладки по осям удобно исполь­зо­вать для цен­тровки. Напри­мер, чтобы отцен­три­ро­вать модуль по гори­зон­тали и вер­ти­кали отно­си­тельно окна бра­у­зера, нужно сде­лать body флекс‑кон­тей­не­ром и выров­нять по цен­тру флекс‑эле­менты по глав­ной и попе­реч­ной осям:

body {
  min-height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

Комбинацию раскладки по осям удобно использовать для центровки. Например, чтобы отцентрировать модуль по горизонтали и вертикали относительно окна браузера, нужно сделать body флекс‑контейнером и выровнять по центру флекс‑элементы по главной и поперечной осям:

body {
  min-height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Любую слож­ную модуль­ную вёрстку можно раз­бить на вло­жен­ные друг в друга флекс‑кон­тей­неры с соб­ствен­ным рит­мом эле­мен­тов. Ска­жем, клас­си­че­ская трёх­ко­лон­ная вёрстка, состо­я­щая из шапки, ста­тьи и двух сайдба­ров, рас­кла­ды­ва­ется в 4 флекс‑контейнера.

Нач­нём с раз­метки трех­ко­ло­ноч­ной страницы:

<header>
  <div>Логотип</div>
  …
</header>

<main>
  <article>
    <p>…</p>
  </article>
  
  <nav>
    <li>Навигация</li>
    …
  </nav>
  
  <aside>
    <div>Реклама</div>
    …
  </aside>
</main>

Пре­вра­тим шапку, содер­жи­мое и сайдбары во флекс‑кон­тей­неры и рас­кра­сим флекс‑эле­менты для наглядности:

header,
main,
aside,
nav {
  display: flex;
}

header > *,
article > *,
aside > *,
nav > * {
  min-height: 36px;
  padding: 9px;
  margin: 9px;
  background: #e5f5ff;
}

Обра­тите вни­ма­ние: по умол­ча­нию флекс‑эле­менты раз­ло­жи­лись в строку, flex-direction: row

Дадим стра­нице всю доступ­ную высоту, раз­ло­жим по вер­ти­кали и рас­тя­нем её содержимое:

body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

main {
  flex-grow: 1; /* Это заклинание заставляет <main> занять всё свободное пространство. Мы рассмотрим его чуть позже */
}

В левом сайдбаре раз­ло­жим эле­менты сверху вниз:

nav {
  flex-direction: column;
  justify-content: flex-start;
}

В пра­вом сайдбаре рав­но­мерно рас­пре­де­лим элементы:

aside {
  flex-direction: column;
  justify-content: space-between;
}

И поме­няем поря­док эле­мен­тов, чтобы колонка с нави­га­цией была слева:

nav {
  flex-direction: column;
  justify-content: flex-start;
  order: -1; /* И это свойство мы рассмотрим чуть позже */
}

По умол­ча­нию флекс‑эле­менты зани­мают столько места, сколько нужно их содер­жи­мому. Поэтому колонки с нави­га­цией и рекла­мой сжались.

Чтобы ширина коло­нок с нави­га­цией и рекла­мой не зави­села тек­ста, под­чи­ним вёрстку пяти­ко­ло­ноч­ной сетке. Зада­дим про­пор­ции, по кото­рым колонки и ста­тья делят сво­бод­ное место в 1:3:1:

nav {
  flex-direction: column;
  justify-content: flex-start;
  order: -1;
  flex-grow: 1;
  flex-basis: 0;
}

aside {
  flex-direction: column;
  justify-content: space-between;
  flex-grow: 1;
  flex-basis: 0;
}

article {
  flex-grow: 3;
  flex-basis: 0;
}
Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

Логотип
Новости
Проекты
О нас

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

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

Начнём с разметки трехколоночной страницы:

<header>
  <div>Логотип</div>
  …
</header>

<main>
  <article>
    <p>…</p>
  </article>
  
  <nav>
    <li>Навигация</li>
    …
  </nav>
  
  <aside>
    <div>Реклама</div>
    …
  </aside>
</main>

Превратим шапку, содержимое и сайдбары во флекс‑контейнеры и раскрасим флекс‑элементы для наглядности:

header,
main,
aside,
nav {
  display: flex;
}

header > *,
article > *,
aside > *,
nav > * {
  min-height: 36px;
  padding: 9px;
  margin: 9px;
  background: #e5f5ff;
}

Обратите внимание: по умолчанию флекс‑элементы разложились в строку, flex-direction: row

Дадим странице всю доступную высоту, разложим по вертикали и растянем её содержимое:

body {
  min-height: 100%;
  display: flex;
  flex-direction: column;
}

main {
  flex-grow: 1; /* Это заклинание заставляет <main> занять всё свободное пространство. Мы рассмотрим его чуть позже */
}

В левом сайдбаре разложим элементы сверху вниз:

nav {
  flex-direction: column;
  justify-content: flex-start;
}

В правом сайдбаре равномерно распределим элементы:

aside {
  flex-direction: column;
  justify-content: space-between;
}

И поменяем порядок элементов, чтобы колонка с навигацией была слева:

nav {
  flex-direction: column;
  justify-content: flex-start;
  order: -1; /* И это свойство мы рассмотрим чуть позже */
}

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

Чтобы ширина колонок с навигацией и рекламой не зависела текста, подчиним вёрстку пятиколоночной сетке. Зададим пропорции, по которым колонки и статья делят свободное место в 1:3:1:

nav {
  flex-direction: column;
  justify-content: flex-start;
  order: -1;
  flex-grow: 1;
  flex-basis: 0;
}

aside {
  flex-direction: column;
  justify-content: space-between;
  flex-grow: 1;
  flex-basis: 0;
}

article {
  flex-grow: 3;
  flex-basis: 0;
}

Упражнение: выравнивание флексбоксом

Изме­ните ЦСС так, чтобы пер­вый ука­за­тель повто­рил второй.


  
align-items: flex-end;
display: flex;
display: flex;
flex-flow: column;
justify-content: space-between;
margin-left: auto;
.plate {
}
.plate__header,
.plate__footer {
}
.plate__footer {
}
.plate__floor,
.plate__right {
}

Вёрстка

  • Платформы 1⁠—4

  • Макдональдс

  • Бюро находок

  • Платформы 5⁠—8

  • Камеры хранения

  • Туалет

  • Кассы

Дизайн

  • Платформы 1⁠—4

  • Макдональдс

  • Бюро находок

  • Платформы 5⁠—8

  • Камеры хранения

  • Туалет

  • Кассы

Вёрстка

  • Платформы 1⁠—4

  • Макдональдс

  • Бюро находок

  • Платформы 5⁠—8

  • Камеры хранения

  • Туалет

  • Кассы

Дизайн

  • Платформы 1⁠—4

  • Макдональдс

  • Бюро находок

  • Платформы 5⁠—8

  • Камеры хранения

  • Туалет

  • Кассы

Упражнение: выравнивание флексбоксом

Измените ЦСС так, чтобы первый указатель повторил второй.


  
align-items: flex-end;
display: flex;
display: flex;
flex-flow: column;
justify-content: space-between;
margin-left: auto;
.plate {
}
.plate__header,
.plate__footer {
}
.plate__footer {
}
.plate__floor,
.plate__right {
}

Перенос флекс‑элементов

По умол­ча­нию флекс‑кон­тей­нер одно­строч­ный: невме­ща­ю­щи­еся эле­менты пере­пол­няют его. Чтобы сде­лать его мно­го­строч­ным и раз­ре­шить пере­нос эле­мен­тов, исполь­зуют flex-wrap: wrap или flex-wrap: wrap-reverse

При этом flex-wrap: wrap-reverse рас­кла­ды­вает невме­ща­ю­щи­еся эле­менты в направ­ле­нии, про­ти­во­по­лож­ном попе­реч­ной оси: снизу вверх

По умолчанию

1
2
3
4
5
6
7
8
9

flex‑wrap: wrap

1
2
3
4
5
6
7
8
9

flex‑wrap: wrap‑reverse

1
2
3
4
5
6
7
8
9

Перенос флекс‑элементов

По умолчанию флекс‑контейнер однострочный: невмещающиеся элементы переполняют его. Чтобы сделать его многострочным и разрешить перенос элементов, используют flex-wrap: wrap или flex-wrap: wrap-reverse

При этом flex-wrap: wrap-reverse раскладывает невмещающиеся элементы в направлении, противоположном поперечной оси: снизу вверх

По умолчанию

1
2
3
4
5
6
7
8
9

flex‑wrap: wrap

1
2
3
4
5
6
7
8
9

flex‑wrap: wrap‑reverse

1
2
3
4
5
6
7
8
9

При смене направ­ле­ния глав­ной оси меня­ется и пове­де­ние пере­носа: flex-wrap: wrap рас­кла­ды­вает невме­ща­ю­щи­еся колонки по попе­реч­ной оси, wrap-reverse — по попе­реч­ной, но в обрат­ном порядке.

flex‑direction: column и flex‑wrap: wrap

1
2
3
4
5
6
7
8
9

flex‑direction: column и flex‑wrap: wrap‑reverse

1
2
3
4
5
6
7
8
9

При смене направления главной оси меняется и поведение переноса: flex-wrap: wrap раскладывает невмещающиеся колонки по поперечной оси, wrap-reverse — по поперечной, но в обратном порядке.

flex‑direction: column и flex‑wrap: wrap

1
2
3
4
5
6
7
8
9

flex‑direction: column и flex‑wrap: wrap‑reverse

1
2
3
4
5
6
7
8
9

В мно­го­строч­ных кон­тей­не­рах за вырав­ни­ва­ние строк на попе­реч­ной оси отве­чает свой­ство align-content

align‑content: stretch

1
2
3
4
5
6
7
8
9

По умолчанию строки растягиваются на всю поперечную ось

align‑content: space‑around

1
2
3
4
5
6
7
8
9

Строки равномерно распределены

align‑content: space‑between

1
2
3
4
5
6
7
8
9

Первая и последняя строки отжаты к краям, оставшееся место делится поровну

align‑content: flex‑start

1
2
3
4
5
6
7
8
9

Строки прижаты к началу поперечной оси

align‑content: center

1
2
3
4
5
6
7
8
9

Строки прижаты к центру поперечной оси

align‑content: flex‑end

1
2
3
4
5
6
7
8
9

Строки прижаты к концу поперечной оси

В многострочных контейнерах за выравнивание строк на поперечной оси отвечает свойство align-content

align‑content: stretch

1
2
3
4
5
6
7
8
9

По умолчанию строки растягиваются на всю поперечную ось

align‑content: space‑around

1
2
3
4
5
6
7
8
9

Строки равномерно распределены

align‑content: space‑between

1
2
3
4
5
6
7
8
9

Первая и последняя строки отжаты к краям, оставшееся место делится поровну

align‑content: flex‑start

1
2
3
4
5
6
7
8
9

Строки прижаты к началу поперечной оси

align‑content: center

1
2
3
4
5
6
7
8
9

Строки прижаты к центру поперечной оси

align‑content: flex‑end

1
2
3
4
5
6
7
8
9

Строки прижаты к концу поперечной оси

Есть два спо­соба задать про­светы между эле­мен­тами. Пер­вый — задать margin у флекс‑элементов:

.columns > * {
  margin: 20px 10px;
}

Вто­рой спо­соб — задать про­светы между эле­мен­тами с помо­щью gap у контейнера:

.columns {
  gap: 40px 20px;
}

Мар­жин даёт допол­ни­тель­ные отступы до и после всех эле­мен­тов. Чтобы не обну­лять и не ком­пен­си­ро­вать их сосед­ними эле­мен­тами, лучше исполь­зо­вать gap

margin: 20px 10px

1
2
3
4
5
6
7
8
9

gap: 40px 20px

1
2
3
4
5
6
7
8
9

Есть два способа задать просветы между элементами. Первый — задать margin у флекс‑элементов:

.columns > * {
  margin: 20px 10px;
}

margin: 20px 10px

1
2
3
4
5
6
7

Второй способ — задать просветы между элементами с помощью gap у контейнера:

.columns {
  gap: 40px 20px;
}

Маржин даёт дополнительные отступы до и после всех элементов. Чтобы не обнулять и не компенсировать их соседними элементами, лучше использовать gap

gap: 40px 20px

1
2
3
4
5
6
7

Чтобы лучше понять флексбоксы, проведём аналогию с авто‑лейаутом из Фигмы

Стрелки в левой колонке отвечают за направление главной оси:

  • flex‑wrap: wrap

  • flex‑direction: row

  • flex‑direction: column

Расстояние между элементами задаётся с помощью gap

Отступы снаружи элементов задаются с помощью свойств контейнера:

  • padding‑left и padding‑right

  • padding‑top и padding‑bottom

Выравнивание задаётся комбинацией свойств:

  • justify‑content: flex‑start; align‑items: flex‑start
  • justify‑content: center; align‑items: flex‑start
  • justify‑content: flex‑end; align‑items: flex‑start
  • justify‑content: flex‑start; align‑items: center
  • justify‑content: center; align‑items: center
  • justify‑content: flex‑end; align‑items: center
  • justify‑content: flex‑start; align‑items: flex‑end
  • justify‑content: center; align‑items: flex‑end
  • justify‑content: flex‑end; align‑items: flex‑end

flex‑wrap: wrap

flex‑direction: row

flex‑direction: column

gap

padding‑left и padding‑right

padding‑top и padding‑bottom

justify‑content: flex‑start; align‑items: flex‑start

justify‑content: center; align‑items: flex‑start

justify‑content: flex‑end; align‑items: flex‑start

justify‑content: flex‑start; align‑items: center

justify‑content: center; align‑items: center

justify‑content: flex‑end; align‑items: center

justify‑content: flex‑start; align‑items: flex‑end

justify‑content: center; align‑items: flex‑end

justify‑content: flex‑end; align‑items: flex‑end

Чтобы лучше понять флексбоксы, проведём аналогию с авто лейаутом из Фигмы.

Стрелки в левой колонке отвечают за направление главной оси:

  • flex‑wrap: wrap
  • flex‑direction: row
  • flex‑direction: column

Расстояние между элементами задаётся с помощью gap

Отступы снаружи элементов задаются с помощью свойств контейнера:

  • padding‑left и padding‑right
  • padding‑top и padding‑bottom

Выравнивание задаётся комбинацией свойств:

  • justify‑content: flex‑start; align‑items: flex‑start
  • justify‑content: center; align‑items: flex‑start
  • justify‑content: flex‑end; align‑items: flex‑start
  • justify‑content: flex‑start; align‑items: center
  • justify‑content: center; align‑items: center
  • justify‑content: flex‑end; align‑items: center
  • justify‑content: flex‑start; align‑items: flex‑end
  • justify‑content: center; align‑items: flex‑end
  • justify‑content: flex‑end; align‑items: flex‑end

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

Нач­нём с раз­метки вари­ан­тов ответов:

<ul class="replies">
  <li>Иди лесом</li>
  <li>Иди опушкой</li>
  <li>Нет!</li>
  <li>Позови человека</li>
  <li>Не хотелось бы хвастаться. Давайте лучше обсудим вашу задачу</li>
  <li>Нет, благодарю. Я готов начинать</li>
</ul>

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

.replies {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

Ука­жем вырав­ни­ва­ние и вклю­чим пере­нос. Теперь эле­менты списка не пыта­ются вме­ститься в одну строчку и вырав­ни­ва­ются по пра­вому краю. Пере­нос про­изой­дёт только в тех эле­мен­тах, кото­рым не хва­тит всей ширины контейнера.

.replies {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
  justify-content: flex-end; /* Прижимаем к правому краю */
  align-content: flex-start; /* И вверх по поперечной оси */
  flex-wrap: wrap;
}

Зада­дим отступ между элементами:

.replies {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
  flex-direction: row;
  justify-content: flex-end;
  align-content: flex-start;
  flex-wrap: wrap;
  gap: 10px;
}

И настроим оформ­ле­ние вари­ан­тов ответов:

.replies li {
  padding: 10px 20px;
  border-radius: 9px;
  border: 1px solid #0b93f6;
  font: 18px/24px Helvetica Neue, Helvetica, Arial, sans-serif;
}

.replies li:hover {
  background: #0b93f6;
  color: #fff;
  cursor: pointer; /* Меняем курсор со стрелочки на курсор, который обычно появляется над нажимаемыми элементами */
}

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

Начнём с разметки вариантов ответов:

<ul class="replies">
  <li>Иди лесом</li>
  <li>Иди опушкой</li>
  <li>Нет!</li>
  <li>Позови человека</li>
  <li>Не хотелось бы хвастаться. Давайте лучше обсудим вашу задачу</li>
  <li>Нет, благодарю. Я готов начинать</li>
</ul>

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

.replies {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

Укажем выравнивание и включим перенос. Теперь элементы списка не пытаются вместиться в одну строчку и выравниваются по правому краю. Перенос произойдёт только в тех элементах, которым не хватит всей ширины контейнера.

.replies {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
  justify-content: flex-end; /* Прижимаем к правому краю */
  align-content: flex-start; /* И вверх по поперечной оси */
  flex-wrap: wrap;
}

Зададим отступ между элементами:

.replies {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
  flex-direction: row;
  justify-content: flex-end;
  align-content: flex-start;
  flex-wrap: wrap;
  gap: 10px;
}

И настроим оформление вариантов ответов:

.replies li {
  padding: 10px 20px;
  border-radius: 9px;
  border: 1px solid #0b93f6;
  font: 18px/24px Helvetica Neue, Helvetica, Arial, sans-serif;
}

.replies li:hover {
  background: #0b93f6;
  color: #fff;
  cursor: pointer; /* Меняем курсор со стрелочки на курсор, который обычно появляется над нажимаемыми элементами */
}

Один элемент с flex‑basis: 50%

Занимает ровно половину контейнера, потому что ему не разрешено увеличиваться

С flex‑grow: 1 вытягивается, чтобы занять всё доступное пространство

Три элемента с flex‑basis: 50%

Переполняют контейнер, потому что им не разрешено уменьшаться

C flex‑shrink: 1 вписываются в контейнер, равномерно уменьшаясь в размерах

Размеры элементов

По умол­ча­нию флекс‑эле­менты зани­мают ровно столько места, сколько нужно их содер­жи­мому. Чтобы управ­лять их раз­ме­рами, рас­тя­ги­вая, умень­шая или зани­мая опре­де­лён­ную долю, есть три ЦСС‑свой­ства: flex-basis, flex-grow и flex-shrink

flex-basis опре­де­ляет началь­ный раз­мер эле­мента перед при­ме­не­нием осталь­ных свойств. Счи­тайте, что flex-basis задаёт иде­аль­ный или пред­по­ло­жи­тель­ный раз­мер элемента.

flex-grow и flex-shrink при­ме­ня­ются после flex-basis. Бра­у­зер све­ря­ется с ними, чтобы понять, как упа­ко­вать эле­менты, если оста­лось сво­бод­ное место или его не хватило.

flex-basis зада­ётся в еди­ни­цах изме­ре­ния раз­ме­ров: пик­се­лях, про­цен­тах, емах и про­чих. flex-grow и flex-shrink опре­де­ляют соб­ствен­ные отно­си­тель­ные раз­меры эле­мен­тов, поэтому зада­ются без­раз­мер­ными поло­жи­тель­ными числами.

Размеры элементов

По умолчанию флекс‑элементы занимают ровно столько места, сколько нужно их содержимому. Чтобы управлять их размерами, растягивая, уменьшая или занимая определённую долю, есть три ЦСС‑свойства: flex-basis, flex-grow и flex-shrink

flex-basis определяет начальный размер элемента перед применением остальных свойств. Считайте, что flex-basis задаёт идеальный или предположительный размер элемента.

flex-grow и flex-shrink применяются после flex-basis. Браузер сверяется с ними, чтобы понять, как упаковать элементы, если осталось свободное место или его не хватило.

flex-basis задаётся в единицах измерения размеров: пикселях, процентах, емах и прочих. flex-grow и flex-shrink определяют собственные относительные размеры элементов, поэтому задаются безразмерными положительными числами.

Один элемент с flex‑basis: 50%

Занимает ровно половину контейнера, потому что ему не разрешено увеличиваться

С flex‑grow: 1 вытягивается, чтобы занять всё доступное пространство

Три элемента с flex‑basis: 50%

Переполняют контейнер, потому что им не разрешено уменьшаться

C flex‑shrink: 1 вписываются в контейнер, равномерно уменьшаясь в размерах

flex-basis часто исполь­зуют для вёрстки по сетке. Напри­мер, так может выгля­деть про­стей­шая сетка на 4 колонки.

Нач­нём с раз­метки. Без сти­лей колонки заняли всё доступ­ное пространство.

<div class="row">
  <div class="col-4">
    <!-- Колонка на всю ширину страницы -->
  </div>
</div>

<div class="row">
  <div class="col-3">
    <!-- Колонка на три четверти страницы -->
  </div>
  <div class="col-1">
    <!-- Колонка на четверть страницы -->
  </div>
</div>

<div class="row">
  <div class="col-1">
    <!-- Колонка на четверть страницы -->
  </div>
  <div class="col-1"></div>
  <div class="col-1"></div>
  <div class="col-1"></div>
</div>

Сти­ли­зуем .row — флекс‑кон­тей­нер, этаж сетки. Теперь колонки при­жа­лись друг к другу у левого края.

.row {
  display: flex;
  flex-direction: row; /* Раскладываем флекс-элементы по колонкам */
  justify-content: flex-start; /* Прижимаем флекс-элементы к левому краю */
  flex-wrap: nowrap; /* Если места для элементов не хватает, запрещаем их перенос на следующую строку */
  margin-left: -10px; /* Компенсируем межколонник */
  margin-right: -10px;
}

Сти­ли­зуем колонки и зада­дим их раз­меры. Появился меж­ко­лон­ник, а колонки стали зани­мать фик­си­ро­ван­ную ширину.

.col-1,
.col-2,
.col-3,
.col-4 {
  flex-grow: 0; /* Не даём элементу растягиваться больше указанного размера */
  flex-shrink: 0; /* Не даём элементу уменьшаться меньше указанного размера */
  padding-left: 10px; /* Задаём межколонник падингами */
  padding-right: 10px;
}

.col-1 {
  flex-basis: 25%;
}

.col-2 {
  flex-basis: 50%;
}

.col-3 {
  flex-basis: 75%;
}

.col-4 {
  flex-basis: 100%;
}

Обра­тите вни­ма­ние: отступы между колон­ками мы задали не с помо­щью gap, а с помо­щью падин­гов у коло­нок и отри­ца­тель­ных мар­жи­нов у эта­жей. Всё потому, что flex-basis не учи­ты­вает gap: колонки полу­чают точно 25%, 50% и 75% ширины, а допол­ни­тель­ный меж­ко­лон­ник вытал­ки­вает их за пре­делы этажа:

.row {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  flex-wrap: nowrap;
  margin-left: -10px;
  margin-right: -10px;
  gap: 20px;
}

.col-1,
.col-2,
.col-3,
.col-4 {
  flex-grow: 0;
  flex-shrink: 0;
  padding-left: 10px;
  padding-right: 10px;
}

Чтобы колон­ками было проще управ­лять, пара­мет­ри­зуем ширину меж­ко­лон­ника. Для этого нам пона­до­бится ЦСС‑пере­мен­ная и функ­ция calc для расчётов:

.row {
  --gutter: 20px; /* Вводим переменную для межколонника */
  --halfGutter: calc(var(--gutter) / 2); /* Считаем половинку межколонника для маржинов и падингов */
  
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  flex-wrap: nowrap;
  margin-left: calc(-1 * var(--halfGutter)); /* В ЦСС нельзя просто написать -var(--halfGutter), приходится изощряться с умножением на -1 */
  margin-right: calc(-1 * var(--halfGutter));
}

.col-1,
.col-2,
.col-3,
.col-4 {
  flex-grow: 0;
  flex-shrink: 0;
  padding-left: var(--halfGutter);
  padding-right: var(--halfGutter);
}

flex-basis часто используют для вёрстки по сетке. Например, так может выглядеть простейшая сетка на 4 колонки.

Начнём с разметки. Без стилей колонки заняли всё доступное пространство.

<div class="row">
  <div class="col-4">
    <!-- Колонка на всю ширину страницы -->
  </div>
</div>

<div class="row">
  <div class="col-3">
    <!-- Колонка на три четверти страницы -->
  </div>
  <div class="col-1">
    <!-- Колонка на четверть страницы -->
  </div>
</div>

<div class="row">
  <div class="col-1">
    <!-- Колонка на четверть страницы -->
  </div>
  <div class="col-1"></div>
  <div class="col-1"></div>
  <div class="col-1"></div>
</div>

Стилизуем .row — флекс‑контейнер, этаж сетки. Теперь колонки прижались друг к другу у левого края.

.row {
  display: flex;
  flex-direction: row; /* Раскладываем флекс-элементы по колонкам */
  justify-content: flex-start; /* Прижимаем флекс-элементы к левому краю */
  flex-wrap: nowrap; /* Если места для элементов не хватает, запрещаем их перенос на следующую строку */
  margin-left: -10px; /* Компенсируем межколонник */
  margin-right: -10px;
}

Стилизуем колонки и зададим их размеры. Появился межколонник, а колонки стали занимать фиксированную ширину.

.col-1,
.col-2,
.col-3,
.col-4 {
  flex-grow: 0; /* Не даём элементу растягиваться больше указанного размера */
  flex-shrink: 0; /* Не даём элементу уменьшаться меньше указанного размера */
  padding-left: 10px; /* Задаём межколонник падингами */
  padding-right: 10px;
}

.col-1 {
  flex-basis: 25%;
}

.col-2 {
  flex-basis: 50%;
}

.col-3 {
  flex-basis: 75%;
}

.col-4 {
  flex-basis: 100%;
}

Обратите внимание: отступы между колонками мы задали не с помощью gap, а с помощью падингов у колонок и отрицательных маржинов у этажей. Всё потому, что flex-basis не учитывает gap: колонки получают точно 25%, 50% и 75% ширины, а дополнительный межколонник выталкивает их за пределы этажа:

.row {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  flex-wrap: nowrap;
  margin-left: -10px;
  margin-right: -10px;
  gap: 20px;
}

.col-1,
.col-2,
.col-3,
.col-4 {
  flex-grow: 0;
  flex-shrink: 0;
  padding-left: 10px;
  padding-right: 10px;
}

Чтобы колонками было проще управлять, параметризуем ширину межколонника. Для этого нам понадобится ЦСС‑переменная и функция calc для расчётов:

.row {
  --gutter: 20px; /* Вводим переменную для межколонника */
  --halfGutter: calc(var(--gutter) / 2); /* Считаем половинку межколонника для маржинов и падингов */
  
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  flex-wrap: nowrap;
  margin-left: calc(-1 * var(--halfGutter)); /* В ЦСС нельзя просто написать -var(--halfGutter), приходится изощряться с умножением на -1 */
  margin-right: calc(-1 * var(--halfGutter));
}

.col-1,
.col-2,
.col-3,
.col-4 {
  flex-grow: 0;
  flex-shrink: 0;
  padding-left: var(--halfGutter);
  padding-right: var(--halfGutter);
}
flex‑basis: 150px
flex‑basis: 150px

Два элемента с flex‑basis: 150px занимают ровно по 150 пк

flex‑basis: 150px
flex‑basis: 150px; flex‑grow: 1

Первый элемент занимает 150 пк, а второй увеличивается так, чтобы занять всё свободное пространство

flex‑basis: 150px; flex‑grow: 2
flex‑basis: 150px; flex‑grow: 1

Два элемента делят свободное пространство в соотношении 2:1

flex‑basis: 0; flex‑grow: 2
flex‑basis: 0; flex‑grow: 1
flex‑basis: 0; flex‑grow: 2

Три элемента делят контейнер в соотношении 2:1:2

flex‑grow

Свой­ство flex-grow задаёт коэф­фи­ци­ент, опре­де­ля­ю­щий, какую долю сво­бод­ного места полу­чит эле­мент. Пред­по­ло­жим, что в кон­тей­нере с двумя эле­мен­тами у пер­вого эле­мента задан flex-grow: 2, а у вто­рого — flex-grow: 1. Тогда эти эле­менты поде­лят сво­бод­ное про­стран­ство в про­пор­ции 2:1.

flex-grow зада­ётся без­раз­мер­ными поло­жи­тель­ными чис­лами. Эти числа могут быть и дроб­ными: напри­мер, 1,618, золо­тое сече­ние. Зна­че­ние по умол­ча­нию равно 0 — эле­мент полу­чит мини­маль­ный раз­мер по сво­ему содержимому.

flex‑grow

Свойство flex-grow задаёт коэффициент, определяющий, какую долю свободного места получит элемент. Предположим, что в контейнере с двумя элементами у первого элемента задан flex-grow: 2, а у второго — flex-grow: 1. Тогда эти элементы поделят свободное пространство в пропорции 2:1.

flex-grow задаётся безразмерными положительными числами. Эти числа могут быть и дробными: например, 1,618, золотое сечение. Значение по умолчанию равно 0 — элемент получит минимальный размер по своему содержимому.

flex‑basis: 150px
flex‑basis: 150px

Два элемента с flex‑basis: 150px занимают ровно по 150 пк

flex‑basis: 150px
flex‑basis: 150px; flex‑grow: 1

Первый элемент занимает 150 пк, а второй увеличивается так, чтобы занять всё свободное пространство

flex‑basis: 150px; flex‑grow: 2
flex‑basis: 150px; flex‑grow: 1

Два элемента делят свободное пространство в соотношении 2:1

flex‑basis: 0; flex‑grow: 2
flex‑basis: 0; flex‑grow: 1
flex‑basis: 0; flex‑grow: 2

Три элемента делят контейнер в соотношении 2:1:2

Пер­вая колонка
Вто­рая колонка
Тре­тья колонка
Чет­вёр­тая колонка
Пер­вая колонка
Вто­рая колонка
Пер­вая колонка
Вто­рая колонка
Тре­тья колонка
Чет­вёр­тая колонка
Пятая колонка

flex-grow: 1 удобно исполь­зо­вать для созда­ния адап­тив­ных коло­нок, рав­но­мерно деля­щих доступ­ное пространство:

.cols {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 18px;
}

.cols > * {
  flex-grow: 1;
  flex-basis: 0;
}
<div class="cols">
  <div>Первая колонка</div>
  …
  <div>Четвёртая колонка</div>
</div>

<div class="cols">
  <div>Первая колонка</div>
  <div>Вторая колонка</div>
</div>

<div class="cols">
  <div>Первая колонка</div>
  …
  <div>Пятая колонка</div>
</div>
Первая колонка
Вторая колонка
Третья колонка
Чет­вёр­тая колонка
Первая колонка
Вторая колонка
Первая колонка
Вторая колонка
Третья колонка
Чет­вёр­тая колонка
Пятая колонка

flex-grow: 1 удобно использовать для создания адаптивных колонок, равномерно делящих доступное пространство:

.cols {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 18px;
}

.cols > * {
  flex-grow: 1;
  flex-basis: 0;
}
<div class="cols">
  <div>Первая колонка</div>
  …
  <div>Четвёртая колонка</div>
</div>

<div class="cols">
  <div>Первая колонка</div>
  <div>Вторая колонка</div>
</div>

<div class="cols">
  <div>Первая колонка</div>
  …
  <div>Пятая колонка</div>
</div>

Чтобы управ­лять поряд­ком отдель­ных эле­мен­тов во флекс‑кон­тей­нере, исполь­зуют свой­ство order. По умол­ча­нию его зна­че­ние равно 0. Соот­вет­ственно, эле­менты с order: 1 ста­нут в конце глав­ной оси, а эле­менты с order: -1 — в начале.

Возь­мём адап­тив­ные колонки из преды­ду­щего примера:

.cols {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 18px;
}

.cols > * {
  flex-grow: 1;
  flex-basis: 0;
}

Пере­ста­вим вто­рую колонку в начало и дадим ей вдвое больше места:

.cols > *:nth-child(2) {
  order: -1;
  flex-grow: 2;
}

Чаще всего order исполь­зуют для адап­та­ции в мобиль­ной вер­сии. Ска­жем, если в двух колон­ках идёт текст, опи­сы­ва­ю­щий иллю­стра­цию, а в послед­ней колонке сама иллю­стра­ция, то в мобиль­ной вер­сии стоит пере­ста­вить изоб­ра­же­ние в начало с помо­щью order: -1

Пер­вая
колонка
Вто­рая
колонка
Тре­тья
колонка
Чет­вёр­тая
колонка
Пер­вая
колонка
Вто­рая
колонка
Тре­тья
колонка
Чет­вёр­тая
колонка
Первая
колонка
Вторая
колонка
Третья
колонка
Четвёртая
колонка
Первая
колонка
Вторая
колонка
Третья
колонка
Четвёртая
колонка

Чтобы управлять порядком отдельных элементов во флекс‑контейнере, используют свойство order. По умолчанию его значение равно 0. Соответственно, элементы с order: 1 станут в конце главной оси, а элементы с order: -1 — в начале.

Возьмём адаптивные колонки из предыдущего примера:

.cols {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 18px;
}

.cols > * {
  flex-grow: 1;
  flex-basis: 0;
}

Переставим вторую колонку в начало и дадим ей вдвое больше места:

.cols > *:nth-child(2) {
  order: -1;
  flex-grow: 2;
}

Чаще всего order используют для адаптации в мобильной версии. Скажем, если в двух колонках идёт текст, описывающий иллюстрацию, а в последней колонке сама иллюстрация, то в мобильной версии стоит переставить изображение в начало с помощью order: -1

flex‑basis: 400px
flex‑basis: 400px

Два элемента с flex‑basis: 400px вмещаются в контейнер шириной 900 пк

flex‑basis: 600px; flex‑shrink: 0
flex‑basis: 600px; flex‑shrink: 0

Два элемента с flex‑basis: 600px и flex‑shrink: 0 не вмещаются в контейнер

flex‑basis: 600px; flex‑shrink: 0
flex‑basis: 600px; flex‑shrink: 1

Первый элемент занимает 600 пк, а второй уменьшается, чтобы вместиться в контейнер

flex‑basis: 600px; flex‑shrink: 2
flex‑basis: 600px; flex‑shrink: 1

Первый элемент уменьшается в два раза быстрее второго

flex‑shrink

Свой­ство flex-shrink задаёт коэф­фи­ци­ент, опре­де­ля­ю­щий, какую долю недо­ста­ю­щего места отдаст эле­мент. flex-shrink исполь­зуют, чтобы обо­зна­чить, какие эле­менты умень­ша­ются, какие нет и с какой ско­ро­стью. Пред­по­ло­жим, что в кон­тей­нере с двумя эле­мен­тами у пер­вого эле­мента задан flex-shrink: 2, а у вто­рого — flex-shrink: 1. Тогда пер­вый эле­мент осво­бо­дит в два раза больше места для остав­шихся эле­мен­тов, чем второй.

flex-shrink зада­ётся без­раз­мер­ными поло­жи­тель­ными чис­лами. Зна­че­ние по умол­ча­нию равно 1 — если места не хва­тит, эле­менты умень­шатся равномерно.

flex‑shrink

Свойство flex-shrink задаёт коэффициент, определяющий, какую долю недостающего места отдаст элемент. flex-shrink используют, чтобы обозначить, какие элементы уменьшаются, какие нет и с какой скоростью. Предположим, что в контейнере с двумя элементами у первого элемента задан flex-shrink: 2, а у второго — flex-shrink: 1. Тогда первый элемент освободит в два раза больше места для оставшихся элементов, чем второй.

flex-shrink задаётся безразмерными положительными числами. Значение по умолчанию равно 1 — если места не хватит, элементы уменьшатся равномерно.

flex‑basis: 100px
flex‑basis: 100px

Два элемента с flex‑basis: 100px вмещаются в контейнер шириной 345 пк

flex‑basis: 200px; flex‑shrink: 0
flex‑basis: 200px; flex‑shrink: 0

Два элемента с flex‑basis: 200px и flex‑shrink: 0 не вмещаются в контейнер

flex‑basis: 200px; flex‑shrink: 0
flex‑basis: 200px; flex‑shrink: 1

Первый элемент занимает 200 пк, а второй уменьшается, чтобы вместиться в контейнер

flex‑basis: 200px; flex‑shrink: 2
flex‑basis: 200px; flex‑shrink: 1

Первый элемент уменьшается в два раза быстрее второго

Песочница

Поэкс­пе­ри­мен­ти­руйте со свой­ствами флексбокса.

flex‑direction

Направ­ле­ние глав­ной оси

align‑items

Вырав­ни­ва­ние по попе­реч­ной оси

justify‑content

Вырав­ни­ва­ние по глав­ной оси

ЦСС

Примеры

Результат

1
2
3
4

Результат

1
2
3
4

ЦСС

Песочница

Поэкспериментируйте со свойствами флексбокса.

flex‑direction

Направление главной оси

align‑items

Выравнивание по поперечной оси

justify‑content

Выравнивание по главной оси

Примеры

Тест

Выберите верные утверждения о раскладке элементов с помощью position.

absolute располагает элемент по заданным координатам относительно ближайшего родителя с position: relative или относительно body, если родителей с position: relative нет
fixed располагает элемент над страницей, элемент зависает над ней в указанных координатах
static создаёт контекст, в котором будут абсолютно позиционироваться другие элементы
sticky значение по умолчанию, элементы просто выстраиваются друг под другом

Элементы с position: absolute и fixed перестают занимать место в потоке элементов и переносятся в указанные координаты. При этом элемент с position: absolute прокручивается вместе со страницей, а элемент с position: fixed висит над ней

Что в этой книге считается ключевой идеей раскладки флексбоксами?

Управление выравниванием элементов через комментарии разработчикам
Флексбокс фиксирует бюджет и срок сдачи вёрстки, оставляя гибким функционал
Введение главной и поперечной осей, по которым раскладываются флекс‑элементы
Автоматическая раскладка элементов «кирпичной кладкой», как в Пинтересе
Флекс‑элементы перестают занимать место и перемещаются к заданным координатам
Введение направляющих и ячеек, в которые будет раскладываться содержимое
Разработчики придумали флексбоксы чтобы «флексить» — хвастаться своей вёрсткой перед другими

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

Каким свойством включается раскладка флексбоксами внутри элемента?

flex: 1
flex: on
flexbox: true
display: flex
display: flexbox
visibility: flex
align-items: flex

display: flex включит раскладку флексбоксами. Элемент станет флекс‑контейнером, а вложенные в него элементы — флекс‑элементами

Выберите верные утверждения о назначении ЦСС‑свойств.

flex-direction управляет направлением главной оси
flex-direction управляет направлением поперечной оси
justify-content управляет расположением элементов на поперечной оси
justify-content управляет расположением элементов на главной оси
align-items управляет высотой элементов
align-items управляет расположением элементов на поперечной оси
align-items управляет расположением элементов на главной оси

flex‑direction задаёт направление главной оси. justify‑content задаёт выравнивание по главной оси, а align‑items — по поперечной

Какое свойство задаёт выравнивание флекс‑элементам по отдельности на поперечной оси?

align-self
justify-content
self-align
element-position
list-style-position

align‑self отвечает за расположение отдельного элемент на поперечной оси

Что по умолчанию происходит с элементами, не вмещающимися во флекс‑контейнер?

Исчезают вместе с контейнером
Переносятся на следующую строку
Переполняют контейнер
Вызывают ошибку в браузере с просьбой отправить баг‑репорт разработчикам
Переносятся в следующий флекс‑контейнер на странице

По умолчанию флекс‑контейнер однострочный: не вмещающиеся элементы переполняют его

За что отвечает свойство align-content?

За выравнивание флекс‑элементов на главной оси
За выключку текста
За размер и выравнивание изображения
За выравнивание строк на поперечной оси в многострочных флекс‑контейнерах
Свойство маркирует содержимое элемента как выражающее определённые политические взгляды

align‑content управляет выравниванием строк в многострочных флекс‑контейнерах

Какими из этих свойств можно задать отступы между флекс‑элементами?

flex-distance
margin
gap
flex-wrap
list-style-position

Свойство margin задаёт внешние отступы любых элементов, а gap управляет отступами элементов внутри флекс‑контейнера

Выберите верные утверждения о назначении ЦСС‑свойств.

flex-basis рекомендует идеальный или предположительный размер элемента по главной оси
flex-basis включает раскладку флексбоксами
flex-basis управляет выравниванием отдельного взятого элемента на главной оси
flex-grow задаёт коэффициент, определяющий, какую долю свободного места в контейнере получит элемент
Совокупность flex-grow флекс‑элементов задаёт пропорцию, в которой они поделят свободное пространство
flex-grow указывает направление, в котором должен увеличиваться элемент
flex-shrink выключает раскладку флексбоксами
flex-shrink сужает флекс‑контейнер, «отжимая» воздух внутри
flex-shrink задаёт коэффициент, определяющий, насколько сильно уменьшится элемент, если в контейнере не хватит места
flex-shrink приказывает элементу исчезнуть из контейнера, если другим элементам понадобится больше места

flex-basis определяет начальный, идеальный размер элемента. Если в контейнере останется свободное место, flex-grow определит, какую его долю получит элемент. Если места не хватит, flex-shrink определит, как сильно элемент уменьшится, чтобы всё вместилось в контейнер

Для чего используют свойство order?

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

order управляет порядком отдельных флекс‑элементов

Ответьте на все вопросы теста,
чтобы узнать результат.
Результат
↺ Пересдать

Тест

Выберите верные утверждения о раскладке элементов с помощью position.

absolute располагает элемент по заданным координатам относительно ближайшего родителя с position: relative или относительно body, если родителей с position: relative нет
fixed располагает элемент над страницей, элемент зависает над ней в указанных координатах
static создаёт контекст, в котором будут абсолютно позиционироваться другие элементы
sticky значение по умолчанию, элементы просто выстраиваются друг под другом

Элементы с position: absolute и fixed перестают занимать место в потоке элементов и переносятся в указанные координаты. При этом элемент с position: absolute прокручивается вместе со страницей, а элемент с position: fixed висит над ней

Что в этой книге считается ключевой идеей раскладки флексбоксами?

Управление выравниванием элементов через комментарии разработчикам
Флексбокс фиксирует бюджет и срок сдачи вёрстки, оставляя гибким функционал
Введение главной и поперечной осей, по которым раскладываются флекс‑элементы
Автоматическая раскладка элементов «кирпичной кладкой», как в Пинтересе
Флекс‑элементы перестают занимать место и перемещаются к заданным координатам
Введение направляющих и ячеек, в которые будет раскладываться содержимое
Разработчики придумали флексбоксы чтобы «флексить» — хвастаться своей вёрсткой перед другими

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

Каким свойством включается раскладка флексбоксами внутри элемента?

flex: 1
flex: on
flexbox: true
display: flex
display: flexbox
visibility: flex
align-items: flex

display: flex включит раскладку флексбоксами. Элемент станет флекс‑контейнером, а вложенные в него элементы — флекс‑элементами

Выберите верные утверждения о назначении ЦСС‑свойств.

flex-direction управляет направлением главной оси
flex-direction управляет направлением поперечной оси
justify-content управляет расположением элементов на поперечной оси
justify-content управляет расположением элементов на главной оси
align-items управляет высотой элементов
align-items управляет расположением элементов на поперечной оси
align-items управляет расположением элементов на главной оси

flex‑direction задаёт направление главной оси. justify‑content задаёт выравнивание по главной оси, а align‑items — по поперечной

Какое свойство задаёт выравнивание флекс‑элементам по отдельности на поперечной оси?

align-self
justify-content
self-align
element-position
list-style-position

align‑self отвечает за расположение отдельного элемент на поперечной оси

Что по умолчанию происходит с элементами, не вмещающимися во флекс‑контейнер?

Исчезают вместе с контейнером
Переносятся на следующую строку
Переполняют контейнер
Вызывают ошибку в браузере с просьбой отправить баг‑репорт разработчикам
Переносятся в следующий флекс‑контейнер на странице

По умолчанию флекс‑контейнер однострочный: не вмещающиеся элементы переполняют его

За что отвечает свойство align-content?

За выравнивание флекс‑элементов на главной оси
За выключку текста
За размер и выравнивание изображения
За выравнивание строк на поперечной оси в многострочных флекс‑контейнерах
Свойство маркирует содержимое элемента как выражающее определённые политические взгляды

align‑content управляет выравниванием строк в многострочных флекс‑контейнерах

Какими из этих свойств можно задать отступы между флекс‑элементами?

flex-distance
margin
gap
flex-wrap
list-style-position

Свойство margin задаёт внешние отступы любых элементов, а gap управляет отступами элементов внутри флекс‑контейнера

Выберите верные утверждения о назначении ЦСС‑свойств.

flex-basis рекомендует идеальный или предположительный размер элемента по главной оси
flex-basis включает раскладку флексбоксами
flex-basis управляет выравниванием отдельного взятого элемента на главной оси
flex-grow задаёт коэффициент, определяющий, какую долю свободного места в контейнере получит элемент
Совокупность flex-grow флекс‑элементов задаёт пропорцию, в которой они поделят свободное пространство
flex-grow указывает направление, в котором должен увеличиваться элемент
flex-shrink выключает раскладку флексбоксами
flex-shrink сужает флекс‑контейнер, «отжимая» воздух внутри
flex-shrink задаёт коэффициент, определяющий, насколько сильно уменьшится элемент, если в контейнере не хватит места
flex-shrink приказывает элементу исчезнуть из контейнера, если другим элементам понадобится больше места

flex-basis определяет начальный, идеальный размер элемента. Если в контейнере останется свободное место, flex-grow определит, какую его долю получит элемент. Если места не хватит, flex-shrink определит, как сильно элемент уменьшится, чтобы всё вместилось в контейнер

Для чего используют свойство order?

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

order управляет порядком отдельных флекс‑элементов

Ответьте на все вопросы теста, чтобы узнать результат.
Результат
↺ Пересдать

Гриды

CSS grid layout или гриды — это набор свойств для рас­кладки эле­мен­тов на стра­нице или в отдель­ном элементе.

Чтобы вклю­чить гриды, нужно задать эле­менту display: grid и сетку: коли­че­ство строк и столб­цов, раз­меры и рас­сто­я­ния между ними. Эле­мент ста­нет грид‑кон­тей­не­ром, а все вло­жен­ные в него эле­менты ста­нут грид‑эле­мен­тами и вста­нут по сетке.

Отно­си­тесь к гри­дам, как к пря­мо­уголь­ному фор­мату, внутри кото­рого можно опре­де­лить направ­ля­ю­щие — грид‑линии, задав таким обра­зом колонки и строки, и раз­ло­жить в полу­чив­ши­еся ячейки содержимое.

Нач­нём с раз­метки и направляющих:

<div class="grid"></div>
.grid {
  display: grid;
  grid-template-rows: 200px 100px; /* Задаём горизонтальные направляющие, два ряда высотой в 200 и 100 пк */
  grid-template-columns: 150px 150px 200px; /* Задаём вертикальные направляющие, две колонки шириной 150 пк и одну шириной 200 пк */
  gap: 18px 18px; /* Задаём отступы между рядами и колонками */
}

Доба­вим внутрь несколько эле­мен­тов. Эле­менты заняли три верх­ние ячейки.

<div class="grid">
  <div>Первый элемент</div>
  <div>Второй элемент</div>
  <div>Третий элемент</div>
</div>

Доба­вим ещё один эле­мент, но поста­вим его между пер­выми двумя. Новый эле­мент занял вто­рую ячейку и «вытолк­нул» после­ду­ю­щие эле­менты в ниж­ний ряд.

<div class="grid">
  <div>Первый элемент</div>
  <div>Внезапный элемент</div>
  <div>Второй элемент</div>
  <div>Третий элемент</div>
</div>
 
Пер­вый эле­мент
Вне­зап­ный эле­мент
Вто­рой эле­мент
Тре­тий эле­мент
Первый элемент
Внезапный элемент
Второй элемент
Третий элемент

Гриды

CSS grid layout или гриды — это набор свойств для раскладки элементов на странице или в отдельном элементе.

Чтобы включить гриды, нужно задать элементу display: grid и сетку: количество строк и столбцов, размеры и расстояния между ними. Элемент станет грид‑контейнером, а все вложенные в него элементы станут грид‑элементами и встанут по сетке.

Относитесь к гридам, как к прямоугольному формату, внутри которого можно определить направляющие — грид‑линии, задав таким образом колонки и строки, и разложить в получившиеся ячейки содержимое.

Начнём с разметки и направляющих:

<div class="grid"></div>
.grid {
  display: grid;
  grid-template-rows: 200px 100px; /* Задаём горизонтальные направляющие, два ряда высотой в 200 и 100 пк */
  grid-template-columns: 150px 150px 200px; /* Задаём вертикальные направляющие, две колонки шириной 150 пк и одну шириной 200 пк */
  gap: 18px 18px; /* Задаём отступы между рядами и колонками */
}

Добавим внутрь несколько элементов. Элементы заняли три верхние ячейки.

<div class="grid">
  <div>Первый элемент</div>
  <div>Второй элемент</div>
  <div>Третий элемент</div>
</div>

Добавим ещё один элемент, но поставим его между первыми двумя. Новый элемент занял вторую ячейку и «вытолкнул» последующие элементы в нижний ряд.

<div class="grid">
  <div>Первый элемент</div>
  <div>Внезапный элемент</div>
  <div>Второй элемент</div>
  <div>Третий элемент</div>
</div>
 

За вер­ти­каль­ные направ­ля­ю­щие, а сле­до­ва­тельно, за коли­че­ство и раз­мер коло­нок отве­чает свой­ство grid-template-columns. Зада­ётся в любых вели­чи­нах длины: пик­се­лях, em, rem, vw, vh и про­цен­тах от ширины родителя.

.columns {
  display: grid;
  grid-template-columns: 50px 20% auto; /* Первая колонка получит ширину в 50 пк, вторая — 20%, третья — всё оставшееся пространство */
}

За вертикальные направляющие, а следовательно, за количество и размер колонок отвечает свойство grid-template-columns. Задаётся в любых величинах длины: пикселях, em, rem, vw, vh и процентах от ширины родителя.

.columns {
  display: grid;
  grid-template-columns: 50px 20% auto; /* Первая колонка получит ширину в 50 пк, вторая — 20%, третья — всё оставшееся пространство */
}
1fr

grid‑template‑columns: 1fr создаст колонку на всю ширину контейнера

1fr
1fr

grid‑template‑columns: 1fr 1fr поделит контейнер пополам

2fr
1fr

grid‑template‑columns: 2fr 1fr поделит контейнер в соотношении 2:1

1fr
100px
2fr

grid‑template‑columns: 1fr 100px 2fr выделит второй колонке 100 пк, а оставшееся место поделит между первой и третьей колонкой в соотношении 1:2

Чтобы поде­лить кон­тей­нер на колонки в нуж­ной про­пор­ции, исполь­зуют еди­ницу изме­ре­ния fr (от fraction). Для каж­дой «рези­но­вой» колонки ука­жите её долю в fr отно­си­тельно дру­гих коло­нок, также задан­ных в fr.

Абсо­лют­ные еди­ницы и fr можно сме­ши­вать. В этом слу­чае про­пор­ции посчи­та­ются отно­си­тельно сво­бод­ного места, остав­ше­гося после выде­ле­ния фик­си­ро­ван­ных колонок.

Чтобы поделить контейнер на колонки в нужной пропорции, используют единицу измерения fr (от fraction). Для каждой «резиновой» колонки укажите её долю в fr относительно других колонок, также заданных в fr.

Абсолютные единицы и fr можно смешивать. В этом случае пропорции посчитаются относительно свободного места, оставшегося после выделения фиксированных колонок.

1fr

grid‑template‑columns: 1fr создаст колонку на всю ширину контейнера

1fr
1fr

grid‑template‑columns: 1fr 1fr поделит контейнер пополам

2fr
1fr

grid‑template‑columns: 2fr 1fr поделит контейнер в соотношении 2:1

1fr
100px
2fr

grid‑template‑columns: 1fr 100px 2fr выделит второй колонке 100 пк, а оставшееся место поделит между первой и третьей колонкой в соотношении 1:2

1fr
1fr
1fr
1fr
1fr
1fr

grid‑template‑columns: repeat(6, 1fr) — то же самое, что grid‑template‑columns: 1fr 1fr 1fr 1fr 1fr 1fr

2fr
2fr
2fr
1fr
1fr
1fr

grid‑template‑columns: repeat(3, 2fr) repeat(3, 1fr) поделит контейнер в соотношении 2:2:2:1:1:1

50px
1fr
1fr
1fr
1fr
50px

grid‑template‑columns: 50px repeat(4, 1fr) 50px выделит первой и последней колонке по 50 пк, а оставшееся место поделит поровну между четырьмя колонками

Чтобы не дуб­ли­ро­вать опи­са­ния оди­на­ко­вых коло­нок, исполь­зуют функ­цию repeat. Ска­жем, repeat(6, 1fr) создаст шесть направ­ля­ю­щих шири­ной в 1fr. Функ­цию можно исполь­зо­вать в пере­мешку с дру­гими зна­че­ни­ями и повто­рять столько раз, сколько нужно.

Чтобы не дублировать описания одинаковых колонок, используют функцию repeat. Скажем, repeat(6, 1fr) создаст шесть направляющих шириной в 1fr. Функцию можно использовать в перемешку с другими значениями и повторять столько раз, сколько нужно.

1fr
1fr
1fr
1fr
1fr
1fr

grid‑template‑columns: repeat(6, 1fr) — то же самое, что grid‑template‑columns: 1fr 1fr 1fr 1fr 1fr 1fr

2fr
2fr
2fr
1fr
1fr
1fr

grid‑template‑columns: repeat(3, 2fr) repeat(3, 1fr) поделит контейнер в соотношении 2:2:2:1:1:1

50px
1fr
1fr
1fr
1fr
50px

grid‑template‑columns: 50px repeat(4, 1fr) 50px выделит первой и последней колонке по 50 пк, а оставшееся место поделит поровну между четырьмя колонками

Напри­мер, так можно свер­стать рас­пи­са­ние лек­ций курса, в кото­ром в каж­дой строке должно быть 5 пре­вью­шек оди­на­ко­вого размера:

.schedule {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 18px 9px; /* 18 пк между рядами, 9 пк между колонкамии*/
}

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

.schedule {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 18px 9px; /* 18 пк между рядами, 9 пк между колонкамии*/
}

Помимо числа коло­нок функ­ция repeat умеет рабо­тать с клю­че­выми сло­вами auto-fill и auto-fit. Они создают столько коло­нок, сколько поме­стится при теку­щей ширине грид‑контейнера:

.schedule {
  display: grid;
  grid-template-columns: repeat(auto-fill, 150px);
  column-gap: 9px;
  row-gap: 18px;
}

Помимо числа колонок функция repeat умеет работать с ключевыми словами auto-fill и auto-fit. Они создают столько колонок, сколько поместится при текущей ширине грид‑контейнера:

.schedule {
  display: grid;
  grid-template-columns: repeat(auto-fill, 150px);
  column-gap: 9px;
  row-gap: 18px;
}

Если повто­рять колонки с фик­си­ро­ван­ной шири­ной, воз­можна «дыра» справа: в кон­тей­нере шири­ной 330 пик­се­лей и колон­ками шири­ной 150 пик­се­лей будет 2 колонки и 30 «лиш­них» пик­се­лей справа. Чтобы колонки рав­но­мерно рас­тя­ну­лись, заняв остав­ше­еся про­стран­ство, исполь­зуют функ­цию minmax и auto-fill или auto-fit:

.schedule {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); /* Создаём как можно больше колонок, чтобы они заняли всю доступную ширину. При этом ширина колонок должна быть не менее 150 пк */
  gap: 18px; /* 18 пк между колонками и строками */
}

Если повторять колонки с фиксированной шириной, возможна «дыра» справа: в контейнере шириной 330 пикселей и колонками шириной 150 пикселей будет 2 колонки и 30 «лишних» пикселей справа. Чтобы колонки равномерно растянулись, заняв оставшееся пространство, используют функцию minmax и auto-fill или auto-fit:

.schedule {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); /* Создаём как можно больше колонок, чтобы они заняли всю доступную ширину. При этом ширина колонок должна быть не менее 150 пк */
  gap: 18px; /* 18 пк между колонками и строками */
}

Клю­че­вое отли­чие между auto-fill и auto-fit про­яв­ля­ется, когда недо­ста­точно эле­мен­тов, чтобы запол­нить сетку по ука­зан­ным в repeat усло­виям. В таком слу­чае auto-fill создаёт мак­си­маль­ное коли­че­ство коло­нок, вклю­чая пустые:

.schedule {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20%, 1fr));
  gap: 18px;
}

А auto-fit рас­тя­ги­вает колонки, схло­пы­вая пустые:

.schedule {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20%, 1fr));
  gap: 18px;
}

Ключевое отличие между auto-fill и auto-fit проявляется, когда недостаточно элементов, чтобы заполнить сетку по указанным в repeat условиям. В таком случае auto-fill создаёт максимальное количество колонок, включая пустые:

.schedule {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(20%, 1fr));
  gap: 18px;
}

А auto-fit растягивает колонки, схлопывая пустые:

.schedule {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(20%, 1fr));
  gap: 18px;
}

За гори­зон­таль­ные направ­ля­ю­щие, за коли­че­ство и раз­мер строк отве­чает свой­ство grid-template-rows. Рабо­тает точно так же, как и grid-template-columns, но для строк.

Под­дер­жи­вает те же величины:

.grid {
  display: grid;
  grid-template-rows: 50px 20% 1fr 2fr;
}

Функ­цию repeat:

.grid {
  display: grid;
  grid-template-rows: repeat(4, 50px);
}

Функ­цию minmax с auto-fill и auto-fit:

.grid {
  display: grid;
  grid-template-rows: repeat(auto-fit, minmax(100px, 1fr));
}
50px
20%
1fr
2fr
50px
50px
50px
50px
minmax(50px, 1fr)
minmax(50px, 1fr)
minmax(50px, 1fr)
minmax(50px, 1fr)
50px
20%
1fr
2fr
50px
50px
50px
50px
minmax(50px, 1fr)
minmax(50px, 1fr)
minmax(50px, 1fr)
minmax(50px, 1fr)

За горизонтальные направляющие, за количество и размер строк отвечает свойство grid-template-rows. Работает точно так же, как и grid-template-columns, но для строк.

Поддерживает те же величины:

.grid {
  display: grid;
  grid-template-rows: 50px 20% 1fr 2fr;
}

Функцию repeat:

.grid {
  display: grid;
  grid-template-rows: repeat(4, 50px);
}

Функцию minmax с auto-fill и auto-fit:

.grid {
  display: grid;
  grid-template-rows: repeat(auto-fit, minmax(100px, 1fr));
}

За поря­док, в кото­ром эле­менты запол­няют кон­тей­нер, отве­чает grid-auto-flow. По умол­ча­нию эле­менты запол­няют его ряд за рядом:

1
2
3
4
.grid {
  grid-auto-flow: row;
}

Если сме­нить grid-auto-flow на column, эле­менты ста­нут запол­нять грид‑кон­тей­нер колонку за колон­кой, сверху вниз:

1
2
3
4
.grid {
  grid-auto-flow: column;
}

За порядок, в котором элементы заполняют контейнер, отвечает grid-auto-flow. По умолчанию элементы заполняют его ряд за рядом:

1
2
3
4
.grid {
  grid-auto-flow: row;
}

Если сменить grid-auto-flow на column, элементы станут заполнять грид‑контейнер колонку за колонкой, сверху вниз:

1
2
3
4
.grid {
  grid-auto-flow: column;
}
1
2
3
4
1
2
3
4
5
6
7
1
2
3
4
5
6
7
1
2
3
4
5
6
7

Собе­рём сетку на четыре элемента:

.grid {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 50px 50px;
}

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

За раз­меры неявно создан­ных ячеек отве­чают свой­ства grid-auto-columns и grid-auto-rows. Доба­вим grid-auto-rows: 50px, чтобы новые ячейки полу­чили высоту в 50 пикселей:

.grid {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 50px 50px;
  grid-auto-rows: 50px; /* Пусть автоматические ряды имеют высоту в 50 пк */
}

Если эле­менты запол­няют кон­тей­нер не по стро­кам, а по колон­кам, исполь­зуем grid-auto-columns

.grid {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 50px 50px;
  grid-auto-flow: column;
  grid-auto-columns: 50px; /* Пусть автоматические колонки имеют ширину в 50 пк */
}
1
2
3
4
1
2
3
4
5
6
7
1
2
3
4
5
6
7
1
2
3
4
5
6
7

Соберём сетку на четыре элемента:

.grid {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 50px 50px;
}

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

За размеры неявно созданных ячеек отвечают свойства grid-auto-columns и grid-auto-rows. Добавим grid-auto-rows: 50px, чтобы новые ячейки получили высоту в 50 пикселей:

.grid {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 50px 50px;
  grid-auto-rows: 50px; /* Пусть автоматические ряды имеют высоту в 50 пк */
}

Если элементы заполняют контейнер не по строкам, а по колонкам, используем grid-auto-columns

.grid {
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 50px 50px;
  grid-auto-flow: column;
  grid-auto-columns: 50px; /* Пусть автоматические колонки имеют ширину в 50 пк */
}

С помо­щью grid-auto-columns и grid-auto-flow можно собрать адап­тив­ные колонки, рав­но­мерно деля­щие доступ­ное пространство:

.cols {
  display: grid; /* Включаем гриды */
  grid-auto-columns: minmax(100px, 1fr); /* Пусть неявные колонки равномерно делят свободное пространство, сохраняя минимальную ширину в 100 пк */
  grid-auto-flow: column; /* Пусть невлезающие элементы выстраиваются в колонки */
  gap: 18px; /* Задаём межколонник в 18 пк */
  height: 100%; /* Растягиваем колонки по высоте */
}
<div class="cols">
  <div>Первая колонка</div>
  …
  <div>Четвёртая колонка</div>
</div>

<div class="cols">
  <div>Первая колонка</div>
  <div>Вторая колонка</div>
</div>

<div class="cols">
  <div>Первая колонка</div>
  …
  <div>Пятая колонка</div>
</div>
Пер­вая колонка
Вто­рая колонка
Тре­тья колонка
Чет­вёр­тая колонка
Пер­вая колонка
Вто­рая колонка
Пер­вая колонка
Вто­рая колонка
Тре­тья колонка
Чет­вёр­тая колонка
Пятая колонка
Первая колонка
Вторая колонка
Третья колонка
Чет­вёр­тая колонка
Первая колонка
Вторая колонка
Первая колонка
Вторая колонка
Третья колонка
Чет­вёр­тая колонка
Пятая колонка

С помощью grid-auto-columns и grid-auto-flow можно собрать адаптивные колонки, равномерно делящие доступное пространство:

.cols {
  display: grid; /* Включаем гриды */
  grid-auto-columns: minmax(100px, 1fr); /* Пусть неявные колонки равномерно делят свободное пространство, сохраняя минимальную ширину в 100 пк */
  grid-auto-flow: column; /* Пусть невлезающие элементы выстраиваются в колонки */
  gap: 18px; /* Задаём межколонник в 18 пк */
  height: 100%; /* Растягиваем колонки по высоте */
}
<div class="cols">
  <div>Первая колонка</div>
  …
  <div>Четвёртая колонка</div>
</div>

<div class="cols">
  <div>Первая колонка</div>
  <div>Вторая колонка</div>
</div>

<div class="cols">
  <div>Первая колонка</div>
  …
  <div>Пятая колонка</div>
</div>

Ком­би­на­цию grid-auto-columns с клю­че­выми сло­вами min-content, max-content или fit-content исполь­зуют для рас­кладки, в кото­рой ширина и коли­че­ство коло­нок зави­сит от содер­жи­мого. Напри­мер, при вёрстке гори­зон­таль­ного меню:

.menu {
  display: grid;
  grid-auto-flow: column; /* Автоматически раскладываем содержимое в колонки */
  grid-auto-columns: max-content; /* Автоматически создаём колонки по количеству пунктов меню с шириной по их содержимому. Мы используем max-content, а не min-content, чтобы пункты меню из нескольких слов всегда оставались в одну строку */
  column-gap: 20px; /* Задаём межколонник в 20 пк */
}

В слу­чае с тек­стом min‑content — это «займи так мало места, чтобы самое длин­ное слово вме­сти­лось», а max‑content — это «займи так много места, чтобы текст не при­ш­лось переносить».

Комбинацию grid-auto-columns с ключевыми словами min-content, max-content или fit-content используют для раскладки, в которой ширина и количество колонок зависит от содержимого. Например, при вёрстке горизонтального меню:

.menu {
  display: grid;
  grid-auto-flow: column; /* Автоматически раскладываем содержимое в колонки */
  grid-auto-columns: max-content; /* Автоматически создаём колонки по количеству пунктов меню с шириной по их содержимому. Мы используем max-content, а не min-content, чтобы пункты меню из нескольких слов всегда оставались в одну строку */
  column-gap: 20px; /* Задаём межколонник в 20 пк */
}

В случае с текстом min‑content — это «займи так мало места, чтобы самое длинное слово вместилось», а max‑content — это «займи так много места, чтобы текст не пришлось переносить».

По умол­ча­нию эле­менты в грид‑кон­тей­нере после­до­ва­тельно запол­няют его ячей­кой за ячей­кой, дви­га­ясь слева‑направо и сверху‑вниз. Чтобы рас­тя­нуть эле­мент на несколько ячеек, исполь­зуют grid-column и grid-row. С помо­щью них мы ука­зы­ваем началь­ную и конеч­ную направ­ля­ю­щие, соот­вет­ственно, всё про­стран­ство между ними отда­ётся элементу:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* Три колонки, равномерно делящие пространство */
  grid-template-rows: repeat(5, minmax(75px, auto)); /* Пять строк с минимальной высотой в 75 пк */
  gap: 20px;
}
.one {
  grid-column: 1 / 3; /* Займи колонки от первой до третьей направляющей */
  grid-row: 1; /* Встань в первый ряд */
}

.two {
  grid-column: 2 / span 2; /* Займи две колонки, начиная со второй направляющей */
  grid-row: 1 / span 2; /* Займи два ряда, начиная со второй направляющей */
}

.three {
  grid-column: 1; /* Встань в первую колонку */
  grid-row: 2 / 5; /* Займи строки от второй до пятой направляющей */
}

.four {
  grid-column: 3;
  grid-row: 3 / span 2;
}

.five {
  grid-column: 2;
  grid-row: 4 / span 2;
}

.six {
  grid-column: 1;
  grid-row: 5;
}

One
Two
Three
Four
Five
Six
One
Two
Three
Four
Five
Six

По умолчанию элементы в грид‑контейнере последовательно заполняют его ячейкой за ячейкой, двигаясь слева‑направо и сверху‑вниз. Чтобы растянуть элемент на несколько ячеек, используют grid-column и grid-row. С помощью них мы указываем начальную и конечную направляющие, соответственно, всё пространство между ними отдаётся элементу:

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* Три колонки, равномерно делящие пространство */
  grid-template-rows: repeat(5, minmax(75px, auto)); /* Пять строк с минимальной высотой в 75 пк */
  gap: 20px;
}
.one {
  grid-column: 1 / 3; /* Займи колонки от первой до третьей направляющей */
  grid-row: 1; /* Встань в первый ряд */
}

.two {
  grid-column: 2 / span 2; /* Займи две колонки, начиная со второй направляющей */
  grid-row: 1 / span 2; /* Займи два ряда, начиная со второй направляющей */
}

.three {
  grid-column: 1; /* Встань в первую колонку */
  grid-row: 2 / 5; /* Займи строки от второй до пятой направляющей */
}

.four {
  grid-column: 3;
  grid-row: 3 / span 2;
}

.five {
  grid-column: 2;
  grid-row: 4 / span 2;
}

.six {
  grid-column: 1;
  grid-row: 5;
}

Meet Dynamic Island

48MP Main camera

The mastermind behind it all

A battery that’s all in, all day.

Film like a Pro.

Shaky shots, stable video

Always‑On display

Tougher than any smartphone glass

Water resistance

Emergency SOS via satellite

A camera in a class by itselfie.

Если в каче­стве зна­че­ний grid‑column или grid‑row задать auto, бра­у­зер сам решит, что делать с эле­мен­том и его пози­цией: эле­менты раз­ме­стятся в том же порядке, что и в коде, зани­мая по одной ячейке. Но если исполь­зо­вать auto / span N, то эле­мент зай­мёт N коло­нок или строк, начи­ная с теку­щего места. Так можно полу­чить «пли­точ­ную вёрстку», в кото­рой поря­док и раз­меры эле­мен­тов опре­де­ля­ются их раз­мет­кой. Напри­мер, как на про­мо­стра­ни­цах Эпла.

Чтобы свер­стать похо­жие этажи, вклю­чим гриды, зада­дим направ­ля­ю­щие и вве­дём четыре вари­а­ции плиток:

.grid {
  display: grid; /* Включаем раскладку гридами  */
  grid-template-columns: 1.85fr 1fr 1.85fr; /* Задаём резиновые направляющие для колонок: первая и третья в 1,85 раза больше центральной */
  grid-auto-rows: 320px; /* Задаём автоматические направляющие для строк: каждые 320 пикселей */
  gap: 20px; /* Задаём межстрочник и межколонник в 20 пк */
  background: #161617;
}

.tile {
  padding: 15px;
  border-radius: 15px;
  background: #000;
  color: #ffb6ff;
}

/* «Плитка» на одну ячейку */
.tile-1-cols-1-rows {
  grid-column: auto / span 1;
  grid-row: auto / span 1;
}

/* Плитка на одну колонку и две строки */
.tile-1-cols-2-rows {
  grid-column: auto / span 1;
  grid-row: auto / span 2;
}

/* Плитка на две колонки и одну строку */
.tile-2-cols-1-rows {
  grid-column: auto / span 2;
  grid-row: auto / span 1;
}

/* Плитка на 2 колонки и 2 строки */
.tile-2-cols-2-rows {
  grid-column: auto / span 2;
  grid-row: auto / span 2;
}
<div class="grid">
  <div class="tile tile-2-cols-2-rows">
    <h2>Meet Dynamic Island</h2>
  </div>
  <div class="tile tile-1-cols-1-rows">
    <h2>48MP Main camera</h2>
  </div>
  <div class="tile tile-1-cols-1-rows">
    <h2>The mastermind behind it all</h2>
  </div>
  <div class="tile tile-1-cols-1-rows">
    <p>A battery that’s all in, all day.</p>
  </div>
  <div class="tile tile-2-cols-1-rows">
    <p>Film like a Pro.</p>
  </div>
  <div class="tile tile-1-cols-2-rows">
    <p>Shaky shots, stable video</p>
  </div>
  <div class="tile tile-2-cols-2-rows">
    <h2>Always-On display</h2>
  </div>
  <div class="tile tile-2-cols-2-rows">
    <h2>Tougher than any smartphone glass</h2>
  </div>
  <div class="tile tile-1-cols-1-rows">
    <h2>Water resistance</h2>
  </div>
  <div class="tile tile-1-cols-2-rows">
    <h2>Emergency SOS via satellite</h2>
  </div>
  <div class="tile tile-2-cols-1-rows">
    <p>A camera in a class by itselfie.</p>
  </div>
</div>

Meet Dynamic Island

48MP Main camera

The mastermind behind it all

A battery that’s all in, all day.

Film like a Pro.

Shaky shots, stable video

Always‑On display

Tougher than any smartphone glass

Water resistance

Emergency SOS via satellite

A camera in a class by itselfie.

Если в качестве значений grid‑column или grid‑row задать auto, браузер сам решит, что делать с элементом и его позицией: элементы разместятся в том же порядке, что и в коде, занимая по одной ячейке. Но если использовать auto / span N, то элемент займёт N колонок или строк, начиная с текущего места. Так можно получить «плиточную вёрстку», в которой порядок и размеры элементов определяются их разметкой. Например, как на промостраницах Эпла.

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

.grid {
  display: grid; /* Включаем раскладку гридами  */
  grid-template-columns: 1.85fr 1fr 1.85fr; /* Задаём резиновые направляющие для колонок: первая и третья в 1,85 раза больше центральной */
  grid-auto-rows: 320px; /* Задаём автоматические направляющие для строк: каждые 320 пикселей */
  gap: 20px; /* Задаём межстрочник и межколонник в 20 пк */
  background: #161617;
}

.tile {
  padding: 15px;
  border-radius: 15px;
  background: #000;
  color: #ffb6ff;
}

/* «Плитка» на одну ячейку */
.tile-1-cols-1-rows {
  grid-column: auto / span 1;
  grid-row: auto / span 1;
}

/* Плитка на одну колонку и две строки */
.tile-1-cols-2-rows {
  grid-column: auto / span 1;
  grid-row: auto / span 2;
}

/* Плитка на две колонки и одну строку */
.tile-2-cols-1-rows {
  grid-column: auto / span 2;
  grid-row: auto / span 1;
}

/* Плитка на 2 колонки и 2 строки */
.tile-2-cols-2-rows {
  grid-column: auto / span 2;
  grid-row: auto / span 2;
}
<div class="grid">
  <div class="tile tile-2-cols-2-rows">
    <h2>Meet Dynamic Island</h2>
  </div>
  <div class="tile tile-1-cols-1-rows">
    <h2>48MP Main camera</h2>
  </div>
  <div class="tile tile-1-cols-1-rows">
    <h2>The mastermind behind it all</h2>
  </div>
  <div class="tile tile-1-cols-1-rows">
    <p>A battery that’s all in, all day.</p>
  </div>
  <div class="tile tile-2-cols-1-rows">
    <p>Film like a Pro.</p>
  </div>
  <div class="tile tile-1-cols-2-rows">
    <p>Shaky shots, stable video</p>
  </div>
  <div class="tile tile-2-cols-2-rows">
    <h2>Always-On display</h2>
  </div>
  <div class="tile tile-2-cols-2-rows">
    <h2>Tougher than any smartphone glass</h2>
  </div>
  <div class="tile tile-1-cols-1-rows">
    <h2>Water resistance</h2>
  </div>
  <div class="tile tile-1-cols-2-rows">
    <h2>Emergency SOS via satellite</h2>
  </div>
  <div class="tile tile-2-cols-1-rows">
    <p>A camera in a class by itselfie.</p>
  </div>
</div>

justify‑items: stretch, по умолчанию

🕑

justify‑items: center

🕑

justify‑items: start

🕑

justify‑items: end

🕑

Выравнивание

Чтобы выров­нять грид‑эле­менты внутри ячеек, исполь­зуем justify-items у грид‑кон­тей­нера. По умол­ча­нию эле­менты рас­тя­ги­ва­ются на всю ячейку, но можно при­жать их к началу, концу или цен­тру ячейки.

Похо­жее свой­ство во флек­с­бок­сах назы­ва­лось justify-content, но у justify-items свои вари­анты зна­че­ний, и оно не зави­сит от оси рас­кладки — все­гда вырав­ни­вает эле­менты по горизонтали.

Выравнивание

Чтобы выровнять грид‑элементы внутри ячеек, используем justify-items у грид‑контейнера. По умолчанию элементы растягиваются на всю ячейку, но можно прижать их к началу, концу или центру ячейки.

Похожее свойство во флексбоксах называлось justify-content, но у justify-items свои варианты значений, и оно не зависит от оси раскладки — всегда выравнивает элементы по горизонтали.

justify‑items: stretch, по умолчанию

🕑

justify‑items: start

🕑

justify‑items: center

🕑

justify‑items: end

🕑

align‑items: stretch

🕑

align‑items: center

🕑

align‑items: start

🕑

align‑items: end

🕑

Чтобы управ­лять вырав­ни­ва­нием внутри ячеек по вер­ти­кали, исполь­зуем align-items у грид‑контейнера.

Свой­ство align-items рабо­тает и во флек­с­бок­сах, но там зави­сит от направ­ле­ния попе­реч­ной оси.

Чтобы управлять выравниванием внутри ячеек по вертикали, используем align-items у грид‑контейнера.

Свойство align-items работает и во флексбоксах, но там зависит от направления поперечной оси.

align‑items: stretch

🕑

align‑items: start

🕑

align‑items: center

🕑

align‑items: end

🕑
🕑

Чтобы изме­нить вырав­ни­ва­ние отдель­ного эле­мента внутри ячейки, исполь­зуем justify-self и align-self

.cross {
  justify-self: start;
  align-self: start;
}

.pencil {
  justify-self: end;
  align-self: start;
}

.clock {
  justify-self: start;
  align-self: end;
}

.star {
  justify-self: end;
  align-self: end;
}
🕑

Чтобы изменить выравнивание отдельного элемента внутри ячейки, используем justify-self и align-self

.cross {
  justify-self: start;
  align-self: start;
}

.pencil {
  justify-self: end;
  align-self: start;
}

.clock {
  justify-self: start;
  align-self: end;
}

.star {
  justify-self: end;
  align-self: end;
}

Если после созда­ния столб­цов и строк в кон­тей­нере оста­лось сво­бод­ное про­стран­ство, то его можно рас­пре­де­лить, как во флек­с­бок­сах, с помо­щью justify-content и align-content

.grid {
  display: grid;
  gap: 18px;
  grid-template-columns: 180px 180px;
  grid-template-rows: 60px 60px;
  justify-content: start;
  align-content: start;
}
Первый элемент
Второй элемент
Третий элемент
Четвёртый элемент
Первый элемент
Второй элемент
Третий элемент
Четвёртый элемент

Если после создания столбцов и строк в контейнере осталось свободное пространство, то его можно распределить, как во флексбоксах, с помощью justify-content и align-content

.grid {
  display: grid;
  gap: 18px;
  grid-template-columns: 180px 180px;
  grid-template-rows: 60px 60px;
  justify-content: start;
  align-content: start;
}

Упражнение: выравнивание гридами

Изме­ните ЦСС так, чтобы пер­вый ука­за­тель повто­рил второй.


  
align-content: space-between;
align-items: end;
display: grid;
display: grid;
grid-template-columns: min-content min-content;
grid-template-rows: min-content min-content;
justify-content: space-between;
.sign {
}
.sign__header,
.sign__footer {
}
.sign__footer {
}

Вёрстка

  • Платформы 1⁠—4

  • Макдональдс

  • Бюро находок

  • Платформы 5⁠—8

  • Камеры хранения

  • Туалет

  • Кассы

Дизайн

  • Платформы 1⁠—4

  • Макдональдс

  • Бюро находок

  • Платформы 5⁠—8

  • Камеры хранения

  • Туалет

  • Кассы

Вёрстка

  • Платформы 1⁠—4

  • Макдональдс

  • Бюро находок

  • Платформы 5⁠—8

  • Камеры хранения

  • Туалет

  • Кассы

Дизайн

  • Платформы 1⁠—4

  • Макдональдс

  • Бюро находок

  • Платформы 5⁠—8

  • Камеры хранения

  • Туалет

  • Кассы

Упражнение: выравнивание гридами

Измените ЦСС так, чтобы первый указатель повторил второй.


  
align-content: space-between;
align-items: end;
display: grid;
display: grid;
grid-template-columns: min-content min-content;
grid-template-rows: min-content min-content;
justify-content: space-between;
.sign {
}
.sign__header,
.sign__footer {
}
.sign__footer {
}

Где использовать гриды, а где флексбоксы

И гриды, и флек­с­боксы под­дер­жи­ва­ются всеми совре­мен­ными бра­у­зе­рами. Больше того, в неко­то­рых слу­чаях гриды и флек­с­боксы вза­и­мо­за­ме­ня­емы — меня­ешь display: inline-grid на inline-flex, всё работает.

Сове­туем выби­рать между флек­с­бок­сами и гри­дами, ори­ен­ти­ру­ясь на свой опыт, задачу и код: какой вари­ант даст меньше кода, какой проще вос­при­ни­мать и поддерживать?

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

С дру­гой сто­роны, если важно впи­сать эле­менты в сетку из N коло­нок, лучше взять гриды. Код будет лако­нич­нее и понятнее:

Грид

.curriculum {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: .5em;
}

Флексбокс

.curriculum {
  --columns: 4;
  --gap: .5em;
  display: flex;
  flex-wrap: nowrap;
  gap: var(--gap);
}

.curriculum > * {
  flex: 1 1 calc(100% / var(--columns));
  max-width: calc((100% / var(--columns)) - var(--gap));
}

Чтобы мастерски овладеть флексбоксами и гридами, сыграйте в игры:

Где использовать гриды, а где флексбоксы

И гриды, и флексбоксы поддерживаются всеми современными браузерами. Больше того, в некоторых случаях гриды и флексбоксы взаимозаменяемы — меняешь display: inline-grid на inline-flex, всё работает.

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

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

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

Грид

.curriculum {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: .5em;
}

Флексбокс

.curriculum {
  --columns: 4;
  --gap: .5em;
  display: flex;
  flex-wrap: nowrap;
  gap: var(--gap);
}

.curriculum > * {
  flex: 1 1 calc(100% / var(--columns));
  max-width: calc((100% / var(--columns)) - var(--gap));
}

Чтобы мастерски овладеть флексбоксами и гридами, сыграйте в игры:

Адаптивная вёрстка

В отли­чие от печат­ной про­дук­ции, нельзя пред­ска­зать, в каком фор­мате посе­ти­тель уви­дит вёрстку — на ком­пакт­ном теле­фоне или на сверх­ши­ро­ком мони­торе. Если в каком‑то слу­чае люди не смо­гут пол­но­ценно поль­зо­ваться сай­том, они уйдут на дру­гой сайт. Поэтому хоро­ший дизайн и вёрстка ста­ра­ются адап­ти­ро­ваться под раз­ные условия.

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

В этой главе мы изу­чим базо­вую адап­тив­ность для любых сай­тов — под раз­меры и про­пор­ции экрана. Почти все эле­менты вёрстки сами под­стра­и­ва­ются под ширину экрана, вер­сталь­щику доста­точно лишь не сло­мать это пове­де­ние и при необ­хо­ди­мо­сти скорректировать.

Адаптивная вёрстка

В отличие от печатной продукции, нельзя предсказать, в каком формате посетитель увидит вёрстку — на компактном телефоне или на сверхшироком мониторе. Если в каком‑то случае люди не смогут полноценно пользоваться сайтом, они уйдут на другой сайт. Поэтому хороший дизайн и вёрстка стараются адаптироваться под разные условия.

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

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

Пер­вым делом, чтобы стра­ница и её эле­менты под­стра­и­ва­лись под ширину бра­у­зера, нужно доба­вить в head осо­бый мета­тег. Без него в неко­то­рых бра­у­зе­рах стра­ница может откры­ваться в непред­ска­зу­е­мом раз­мере и масштабе.

<head>
  <!-- … -->
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

Боль­шин­ство эле­мен­тов на стра­нице — заго­ловки, абзацы, дивы — блоч­ные. Их ширина адап­тивна по умол­ча­нию — задана как 100% ширины роди­теля. Если не менять эти стили, блоч­ные эле­менты в насле­до­ва­нии дой­дут до body и рас­тя­нутся на всю ширину бра­у­зера. Изме­нится ширина бра­у­зера — изме­нится ширина эле­мен­тов. Попро­буйте изме­нять ширину при­мера, дви­гая за уголок.

Чтобы эле­менты оста­ва­лись адап­тив­ными к ширине роди­теля или бра­у­зера, доста­точно не зада­вать им стро­гую ширину в пик­се­лях, а исполь­зо­вать про­центы или дру­гие отно­си­тель­ные единицы.

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

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

<head>
  <!-- … -->
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

Большинство элементов на странице — заголовки, абзацы, дивы — блочные. Их ширина адаптивна по умолчанию — задана как 100% ширины родителя. Если не менять эти стили, блочные элементы в наследовании дойдут до body и растянутся на всю ширину браузера. Изменится ширина браузера — изменится ширина элементов. Попробуйте изменять ширину примера, двигая за уголок.

Чтобы элементы оставались адаптивными к ширине родителя или браузера, достаточно не задавать им строгую ширину в пикселях, а использовать проценты или другие относительные единицы.

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

Текст внутри эле­мен­тов сам адап­ти­ру­ется под их ширину.

Рас­по­ло­же­ние пере­но­сов в тек­сте зави­сит от ширины эле­мента, поэтому полезно под­стра­хо­ваться: исполь­зо­вать нераз­рыв­ные про­белы или тег nobr, чтобы под­кле­ить пред­логи, союзы, тире и предот­вра­тить непри­ят­ные переносы.

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

Рабо­таем с
1997 года.
Экс­перты на 
рынке авто­услуг.
Зво­ните: 8 800 301‑
64‑40

Рабо­таем
с 1997 года.
Экс­перты на рынке
авто­услуг.
Зво­ните:
8 800 301‑64‑40

Работаем с
1997 года.
Эксперты на 
рынке автоуслуг.
Звоните: 8 800 301‑
64‑40

Работаем
с 1997 года.
Эксперты на рынке
автоуслуг.
Звоните:
8 800 301‑64‑40

Текст внутри элементов сам адаптируется под их ширину.

Расположение переносов в тексте зависит от ширины элемента, поэтому полезно подстраховаться: использовать неразрывные пробелы или тег nobr, чтобы подклеить предлоги, союзы, тире и предотвратить неприятные переносы.

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

Адаптивная высота

Высота эле­мента — это высота его содер­жи­мого плюс раз­меры внут­рен­них осту­пов сверху и снизу. Чем выше содер­жи­мое, тем выше высота элемента.

<div class="box">
  Предлагаю думать о строгих размерах как о гвоздях…
</div>
.box {
  padding: 20px 10px 40px;
}

Если явно задать высоту, то содер­жи­мое может вылезти за гра­ницы элемента.

.box {
  padding: 20px 10px 40px;
  height: 200px;
}

Лучше все­гда исхо­дить из того, что высота содер­жи­мого будет расти. Вёрстка обычно стро­ится сверху вниз, новые эле­менты уве­ли­чи­вают высоту своих роди­те­лей. А на узких экра­нах ширина эле­мента может умень­шиться и текст внутри зай­мёт больше строк. Поэтому лучше не зада­вать и не огра­ни­чи­вать высоту свой­ствами height и max-height, кроме слу­чаев, когда зна­ете зачем.

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

.box {
  padding: 20px 10px;
  min-height: 130px;
}

Пред­ла­гаю думать о стро­гих раз­ме­рах как о гвоз­дях, кото­рые намертво при­ко­ла­чи­вают вёрстку. Ино­гда без гвоз­дей не обой­тись, но в боль­шин­стве слу­чаев они только мешают: уби­вают гиб­кость моду­лей, изме­няют их при­выч­ное пове­де­ние, мешают дора­ба­ты­вать и раз­ви­вать вёрстку, рабо­тать с содержимым.

Пред­ла­гаю думать о стро­гих раз­ме­рах как о гвоз­дях, кото­рые намертво при­ко­ла­чи­вают вёрстку. Ино­гда без гвоз­дей не обой­тись, но в боль­шин­стве слу­чаев они только мешают: уби­вают гиб­кость моду­лей, изме­няют их при­выч­ное пове­де­ние, мешают дора­ба­ты­вать и раз­ви­вать вёрстку, рабо­тать с содержимым.

Каж­дый раз, когда хочется задать стро­гие раз­меры в ЦСС, стоит оста­но­виться и поду­мать, как этого не делать. Лучше потра­тить десять минут на созда­ние надёж­ной и гиб­кой вёрстки сразу, чем в буду­щем убить часы на её доработку.

Кофе‑тайм

★ 4,9 (450) · Средний чек 600₽
Кофейня · Кропоткинская, 95
Открыто · Закроется в 22:30

Кофе с собой · Десерты

Кофе 777

Нет отзывов
Кофейня · Вокзальный переулок

Coffee Like

★ 5 (2) · Средний чек 500₽
Кофейня · Загорская, 32

Кофе с собой

Предлагаю думать о строгих размерах как о гвоздях, которые намертво приколачивают вёрстку. Иногда без гвоздей не обойтись, но в большинстве случаев они только мешают: убивают гибкость модулей, изменяют их привычное поведение, мешают дорабатывать и развивать вёрстку, работать с содержимым.

Предлагаю думать о строгих размерах как о гвоздях, которые намертво приколачивают вёрстку. Иногда без гвоздей не обойтись, но в большинстве случаев они только мешают: убивают гибкость модулей, изменяют их привычное поведение, мешают дорабатывать и развивать вёрстку, работать с содержимым.

Каждый раз, когда хочется задать строгие размеры в ЦСС, стоит остановиться и подумать, как этого не делать. Лучше потратить десять минут на создание надёжной и гибкой вёрстки сразу, чем в будущем убить часы на её доработку.

Кофе‑тайм

★ 4,9 (450) · Средний чек 600₽
Кофейня · Кропоткинская, 95
Открыто · Закроется в 22:30

Кофе с собой · Десерты

Кофе 777

Нет отзывов
Кофейня · Вокзальный переулок

Coffee Like

★ 5 (2) · Средний чек 500₽
Кофейня · Загорская, 32

Кофе с собой

Адаптивная высота

Высота элемента — это высота его содержимого плюс размеры внутренних оступов сверху и снизу. Чем выше содержимое, тем выше высота элемента.

<div class="box">
  Предлагаю думать о строгих размерах как о гвоздях…
</div>
.box {
  padding: 20px 10px 40px;
}

Если явно задать высоту, то содержимое может вылезти за границы элемента.

.box {
  padding: 20px 10px 40px;
  height: 200px;
}

Лучше всегда исходить из того, что высота содержимого будет расти. Вёрстка обычно строится сверху вниз, новые элементы увеличивают высоту своих родителей. А на узких экранах ширина элемента может уменьшиться и текст внутри займёт больше строк. Поэтому лучше не задавать и не ограничивать высоту свойствами height и max-height, кроме случаев, когда знаете зачем.

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

.box {
  padding: 20px 10px;
  min-height: 130px;
}

Поскольку по умол­ча­нию содер­жи­мое адап­ти­ру­ется под ширину эле­мента, а высота эле­мента адап­ти­ру­ется под содер­жи­мое, то пере­пол­не­ние может появиться только если где‑то нару­шена стан­дарт­ная адап­тив­ность, заданы стро­гие размеры.

Поскольку по умол­ча­нию содер­жи­мое адап­ти­ру­ется под ширину эле­мента, а высота эле­мента адап­ти­ру­ется под содер­жи­мое, то пере­пол­не­ние может появиться только если где‑то нару­шена стан­дарт­ная адап­тив­ность, заданы стро­гие размеры.

Поскольку по умол­ча­нию содер­жи­мое адап­ти­ру­ется под ширину эле­мента, а высота эле­мента адап­ти­ру­ется под содер­жи­мое, то пере­пол­не­ние может появиться только если где‑то нару­шена стан­дарт­ная адап­тив­ность, заданы стро­гие размеры.

Переполнение — overflow

Если содер­жи­мое больше ширины или высоты эле­мента, оно выле­зет за его гра­ницы. Это назы­ва­ется переполнением.

Пере­пол­не­ние почти все­гда сиг­на­ли­зи­рует о про­бле­мах с вёрст­кой. Ведь по умол­ча­нию содер­жи­мое эле­мента адап­ти­ру­ется под ширину, а высота адап­ти­ру­ется под содер­жи­мое. Зна­чит, пере­пол­не­ние появится только когда где‑то нару­шена адап­тив­ность, заданы стро­гие раз­меры. Чтобы пере­пол­не­ние не пор­тило вид сайта, нужно устра­нить его причину.

Свой­ство overflow управ­ляет тем, как эле­мент посту­пит с пере­пол­ня­ю­щим содержимым.

overflow: visible — по умол­ча­нию — содер­жи­мое выле­зет за гра­ницы, может налезть на сосед­ние эле­менты или доба­вить стра­нице ненуж­ную прокрутку.

overflow: hidden — содер­жи­мое обре­жется по гра­ни­цам элемента.

overflow: scroll — доба­вит про­крутку не влез­шего содер­жи­мого. Ино­гда исполь­зу­ется наме­ренно, чтобы «впих­нуть нев­пиху­е­мое» в дизайне.

Поскольку по умолчанию содержимое адаптируется под ширину элемента, а высота элемента адаптируется под содержимое, то переполнение может появиться только если где‑то нарушена стандартная адаптивность, заданы строгие размеры.

Поскольку по умолчанию содержимое адаптируется под ширину элемента, а высота элемента адаптируется под содержимое, то переполнение может появиться только если где‑то нарушена стандартная адаптивность, заданы строгие размеры.

Поскольку по умолчанию содержимое адаптируется под ширину элемента, а высота элемента адаптируется под содержимое, то переполнение может появиться только если где‑то нарушена стандартная адаптивность, заданы строгие размеры.

Переполнение — overflow

Если содержимое больше ширины или высоты элемента, оно вылезет за его границы. Это называется переполнением.

Переполнение почти всегда сигнализирует о проблемах с вёрсткой. Ведь по умолчанию содержимое элемента адаптируется под ширину, а высота адаптируется под содержимое. Значит, переполнение появится только когда где‑то нарушена адаптивность, заданы строгие размеры. Чтобы переполнение не портило вид сайта, нужно устранить его причину.

Свойство overflow управляет тем, как элемент поступит с переполняющим содержимым.

overflow: visible — по умолчанию — содержимое вылезет за границы, может налезть на соседние элементы или добавить странице ненужную прокрутку.

overflow: hidden — содержимое обрежется по границам элемента.

overflow: scroll — добавит прокрутку не влезшего содержимого. Иногда используется намеренно, чтобы «впихнуть невпихуемое» в дизайне.

Пропорциональный модуль

Свой­ство aspect-ratio реко­мен­дует эле­менту про­пор­цию ширины к высоте:

.hero {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 4vw;
  aspect-ratio: 16 / 9;

  padding: 1% 3%;
  box-sizing: border-box; /* Включаем размеры падингов в ширину и высоту, чтобы не нарушать пропорцию размерами падингов */
}

Но если высота содер­жи­мого пре­вы­сит высоту эле­мента, то эле­мент вырас­тет и про­пор­ция изме­нится. Поскольку aspect-ratio лишь реко­мен­дует про­пор­цию, а не задаёт раз­меры строго, пере­пол­не­ния не произойдёт.

Пропорциональный модуль

Свойство aspect-ratio рекомендует элементу пропорцию ширины к высоте:

.hero {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  font-size: 4vw;
  aspect-ratio: 16 / 9;

  padding: 1% 3%;
  box-sizing: border-box; /* Включаем размеры падингов в ширину и высоту, чтобы не нарушать пропорцию размерами падингов */
}

Но если высота содержимого превысит высоту элемента, то элемент вырастет и пропорция изменится. Поскольку aspect-ratio лишь рекомендует пропорцию, а не задаёт размеры строго, переполнения не произойдёт.

Медиавыражения

Чтобы при­ме­нить стили при соблю­де­нии усло­вий, свя­зан­ных с фор­ма­том отоб­ра­же­ния, настрой­кой оформ­ле­ния или устрой­ством поль­зо­ва­теля, исполь­зуют медиа­вы­ра­же­ния. Выра­же­ние состоит из усло­вия и набора сти­лей, кото­рые бра­у­зер дол­жен при­ме­нить при его соблюдении.

Чаще всего медиа­вы­ра­же­ние исполь­зуют, чтобы при­ме­нить осо­бые стили на опре­де­лён­ном раз­мере экрана.

Допу­стим, у нас есть стра­ница с тек­сто­вым моду­лем. Сна­чала напи­шем стили по умол­ча­нию, кото­рые будут при­ме­нятся всегда:

<body>
  <div class="text">
    <p>…</p>
    <p>…</p>
    <p>…</p>
  </div>
</body>
body {
  font-size: 24px;
  padding: 24px;
}

.text {
  max-width: 640px;
  margin: 0 auto;
}

Затем допи­шем медиа­вы­ра­же­ние с набо­ром сти­лей для ширины менее 600 пк. На этой ширине бра­у­зер при­ме­нит стили из медиа­вы­ра­же­ния, допол­нив или пере­опре­де­лив напи­сан­ные ранее стили страницы.

body {
  font-size: 24px;
  padding: 32px;
}

.text {
  max-width: 640px;
  margin: 0 auto;
}

@media (width < 600px) {            
  body {
    font-size: 18px;
    padding: 16px;
  }
  
  .text {
    max-width: none;
  }
}

Медиавыражения

Чтобы применить стили при соблюдении условий, связанных с форматом отображения, настройкой оформления или устройством пользователя, используют медиавыражения. Выражение состоит из условия и набора стилей, которые браузер должен применить при его соблюдении.

Чаще всего медиавыражение используют, чтобы применить особые стили на определённом размере экрана.

Допустим, у нас есть страница с текстовым модулем. Сначала напишем стили по умолчанию, которые будут применятся всегда:

<body>
  <div class="text">
    <p>…</p>
    <p>…</p>
    <p>…</p>
  </div>
</body>
body {
  font-size: 24px;
  padding: 24px;
}

.text {
  max-width: 640px;
  margin: 0 auto;
}

Затем допишем медиавыражение с набором стилей для ширины менее 600 пк. На этой ширине браузер применит стили из медиавыражения, дополнив или переопределив написанные ранее стили страницы.

body {
  font-size: 24px;
  padding: 32px;
}

.text {
  max-width: 640px;
  margin: 0 auto;
}

@media (width < 600px) {            
  body {
    font-size: 18px;
    padding: 16px;
  }
  
  .text {
    max-width: none;
  }
}

Проверка размеров экрана

@media (width <= 960px) { 
  /* Ширина экрана до 960 пк включительно */
}

@media (max-width: 960px) { 
  /* То же, что в предыдущем примере */
}

@media (height > 768px) { 
  /* Высота экрана от 768 пк */
}

Проверка отношения сторон экрана

@media (orientation: landscape) {
  /* Ширина экрана больше высоты, наоборот — portrait */
}

@media screen and (max-aspect-ratio: 16 / 9) {
  /* Пропорция экрана до 16:9 */
}

@media screen and (min-aspect-ratio: 16 / 10) {
  /* Пропорция экрана от 16:10 */
}

Проверка настроек темы оформления

@media (prefers-color-scheme: dark) {
  /* На устройстве включена тёмная тема */
}

Проверка возможности наведения

@media (hover: hover) {
  /* На устройстве возможно наведение. Например, это компьютер с мышью или тачпадом, но не смартфон */
}

Комплексные проверки

Несколько усло­вий пере­чис­ляют через and.

@media (orientation: landscape) and (max-width: 767px) {
  /* Горизонтальный экран до 768 пк */
}

@media (orientation: landscape) and (960px <= width <= 1024px) {
  /* Горизонтальный экран на диапазоне ширины 960—1024 пк ширины включительно */
}

Медиа­вы­ра­же­ния поз­во­ляют про­ве­рить не только раз­мер экрана, но и усло­вия фор­мата, настройки оформ­ле­ния или устрой­ства пользователя.

Проверка размеров экрана

@media (width <= 960px) { 
  /* Ширина экрана до 960 пк включительно */
}

@media (max-width: 960px) { 
  /* То же, что в предыдущем примере */
}

@media (height > 768px) { 
  /* Высота экрана от 768 пк */
}

Проверка отношения сторон экрана

@media (orientation: landscape) {
  /* Ширина экрана больше высоты, наоборот — portrait */
}

@media screen and (max-aspect-ratio: 16 / 9) {
  /* Пропорция экрана до 16:9 */
}

@media screen and (min-aspect-ratio: 16 / 10) {
  /* Пропорция экрана от 16:10 */
}

Проверка настроек темы оформления

@media (prefers-color-scheme: dark) {
  /* На устройстве включена тёмная тема */
}

Проверка возможности наведения

@media (hover: hover) {
  /* На устройстве возможно наведение. Например, это компьютер с мышью или тачпадом, но не смартфон */
}

Комплексные проверки

Несколько условий перечисляют через and.

@media (orientation: landscape) and (max-width: 767px) {
  /* Горизонтальный экран до 768 пк */
}

@media (orientation: landscape) and (960px <= width <= 1024px) {
  /* Горизонтальный экран на диапазоне ширины 960—1024 пк ширины включительно */
}

Медиавыражения позволяют проверить не только размер экрана, но и условия формата, настройки оформления или устройства пользователя.

Адаптивные картинки, srcset

Чтобы уско­рить загрузку стра­ницы на теле­фоне, можно пока­зы­вать умень­шен­ные вер­сии картинок.

Раз­ница между кар­тин­ками шири­ной 1440 и 600 пк на неболь­шом экране теле­фона не так заметна. Зато умень­шен­ная кар­тинка гораздо меньше весит и быст­рее загру­зится. Чем больше на стра­нице кар­ти­нок, тем замет­нее будет эффект от такой оптимизации.

В базо­вой записи img одна кар­тинка из src исполь­зу­ется на всех раз­ме­рах экрана:

<img src="img-1440×1849.jpg">

Доба­вим аль­тер­на­тив­ную умень­шен­ную кар­тинку в атри­бут srcset, после назва­ния обя­за­тельно ука­зать её ширину в пик­се­лях с бук­вой w (width):

<img 
  src="img-1440×1849.jpg" 
  srcset="img-600×770.jpg 600w">

Это экви­ва­лентно такой записи, потому что srcset — это все вари­анты картинки:

<img 
  src="img-1440×1849.jpg" 
  srcset="
    img-1440×1849.jpg 1440w,
    img-600×770.jpg 600w">

Но поскольку один из вари­ан­тов уже ука­зан в обя­за­тель­ном атри­буте src, его можно не писать в srcset.

Бра­у­зер в самом начале загрузки стра­ницы умно­жит ширину экрана поль­зо­ва­теля на его плот­ность пик­се­лей и выбе­рет кар­тинку опти­маль­ной ширины для загрузки. В нашем слу­чае, если ширина × плот­ность пик­се­лей больше 600, исполь­зу­ется кар­тинка из src, иначе — умень­шен­ная картинка.

Поскольку бра­у­зеру нужно зара­нее выбрать, какую кар­тинку вообще качать, он не может сам понять ширину кар­ти­нок, ведь для этого их нужно ска­чать. Поэтому нам и нужно под­ска­зать бра­у­зеру ширину аль­тер­на­тив­ных кар­ти­нок в src.

Марк Шагал. Я и деревня. 1911

1440×1849, 917 КБ

600×770, 135 КБ

1440×1849, 917 КБ

600×770, 135 КБ

Адаптивные картинки, srcset

Чтобы ускорить загрузку страницы на телефоне, можно показывать уменьшенные версии картинок.

Разница между картинками шириной 1440 и 600 пк на небольшом экране телефона не так заметна. Зато уменьшенная картинка гораздо меньше весит и быстрее загрузится. Чем больше на странице картинок, тем заметнее будет эффект от такой оптимизации.

В базовой записи img одна картинка из src используется на всех размерах экрана:

<img src="img-1440×1849.jpg">

Добавим альтернативную уменьшенную картинку в атрибут srcset, после названия обязательно указать её ширину в пикселях с буквой w (width):

<img 
  src="img-1440×1849.jpg" 
  srcset="img-600×770.jpg 600w">

Это эквивалентно такой записи, потому что srcset — это все варианты картинки:

<img 
  src="img-1440×1849.jpg" 
  srcset="
    img-1440×1849.jpg 1440w,
    img-600×770.jpg 600w">

Но поскольку один из вариантов уже указан в обязательном атрибуте src, его можно не писать в srcset.

Браузер в самом начале загрузки страницы умножит ширину экрана пользователя на его плотность пикселей и выберет картинку оптимальной ширины для загрузки. В нашем случае, если ширина × плотность пикселей больше 600, используется картинка из src, иначе — уменьшенная картинка.

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

Марк Шагал. Я и деревня. 1911

1440×1019, приемлемо для обычных мониторов, размыто на «ретине»

2800×1982, более чёткая версия для «ретины»

Атри­бут srcset исполь­зуют не только чтобы пока­зать умень­шен­ную вер­сию кар­тинки на теле­фоне, но и в обрат­ную сто­рону — чтобы пока­зать более каче­ствен­ную кар­тинку на боль­ших экра­нах с высо­кой плот­но­стью пик­се­лей, при этом не качая лиш­него на обыч­ных экранах.

Прин­цип тот же — в атри­буте ука­зы­ваем аль­тер­на­тив­ную кар­тинку и под­ска­зы­ваем бра­у­зеру её ширину:

<img 
  src="img-1440×1019.jpg"
  srcset="img-2800×1982.jpg 2800w">

Через запя­тую в атри­буте можно пере­чис­лить сразу много вари­ан­тов для раз­ных экранов:

<img 
  src="img-1440×1019.jpg"
  srcset="
    img-360x255.jpg 360w,
    img-720x509.jpg 720w,
    img-2800×1982.jpg 2800w">

Дру­гой вопрос — стоит ли замо­ра­чи­ваться и про­пи­сы­вать много вари­ан­тов кар­ти­нок, потому что их при­дётся под­го­тав­ли­вать. В про­дви­ну­той раз­ра­ботке вари­анты кар­ти­нок созда­ются авто­ма­ти­че­ски, код для них гене­ри­ру­ется. В руч­ной вёрстке под­дер­жи­вать больше пары вер­сий кар­ти­нок тяжело и обычно нецелесообразно.

Марк Шагал. Над городом. 1918

1440×1019, приемлемо для обычных мониторов, размыто на «ретине»

2800×1982, более чёткая версия для «ретины»

Атрибут srcset используют не только чтобы показать уменьшенную версию картинки на телефоне, но и в обратную сторону — чтобы показать более качественную картинку на больших экранах с высокой плотностью пикселей, при этом не качая лишнего на обычных экранах.

Принцип тот же — в атрибуте указываем альтернативную картинку и подсказываем браузеру её ширину:

<img 
  src="1440.jpg"
  srcset="2800.jpg 2800w">

Через запятую в атрибуте можно перечислить сразу много вариантов для разных экранов:

<img 
  src="1440.jpg"
  srcset="
    360.jpg 360w,
    720.jpg 720w,
    2800.jpg 2800w">

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

Марк Шагал. Над городом. 1918

Пока img с атри­бу­том srcset все­гда зани­мает 100% ширины экрана, всё рабо­тает отлично, бра­у­зер знает ширину экрана и под­бе­рёт под неё опти­маль­ную кар­тинку из име­ю­щихся в src и srcset.

<img 
  src="img-1000.jpg"
  srcset="img-1400.jpg 1400w">

Но допу­стим, кар­тинка на широ­ком экране зани­мает поло­вину ширины:

img {
  width: 100%;
}

@media (width > 960px) {
  img {
    width: 50%;
  }
}
<img 
  src="img-1000.jpg"
  srcset="img-1400.jpg 1400w">

Бра­у­зер на момент выбора кар­тинки не знает ширину img в вёрстке — выбор про­ис­хо­дит до постро­е­ния стра­ницы. Поэтому и в этом слу­чае он выбе­рет кар­тинку под всю ширину экрана.

Под­ска­жем бра­у­зеру воз­мож­ные раз­меры эле­мента в атри­буте sizes. В конце — раз­мер по умол­ча­нию, исполь­зу­ется, если ни одно усло­вие выше не удовлетворено.

img {
  width: 100%;
}

@media (width > 960px) {
  img {
    width: 50%;
  }
}
<!-- Проще всего взять медиавыражение из ЦСС, которое определяет ширину картинки, и перенести значения в sizes, заменяя проценты на vw -->

<img 
  src="img-1000.jpg"
  srcset="img-1400.jpg 1400w"
  sizes="
    (width > 960px) 50vw,
    100vw">

Теперь бра­у­зер знает, что кар­тинка на ком­пью­тере зани­мает поло­вину ширины и выбе­рет опти­маль­ную кар­тинку. Если раз­ме­ров много, сле­дите за их поряд­ком, бра­у­зер выбе­рет пер­вое сра­бо­тав­шее усло­вие и не пой­дёт дальше.

img‑1000.jpg

Ширина экрана 375, оптимальная картинка из имеющихся — img‑1000

img‑1400.jpg

1400, оптимальная картинка — img‑1440

img‑1000.jpg

Ширина экрана 375, оптимальная картинка из имеющихся — img‑1000

img‑1400.jpg

1400, но картинка занимает только половину, img‑1400 избыточно, достаточно img‑1000

img‑1000.jpg

Ширина экрана 375, оптимальная картинка из имеющихся — img‑1000

img‑1000.jpg

1400, браузер знает, что картинка занимает половину и правильно выбирает оптимальную картинку — img‑1000

1400.jpg
1000.jpg

На ширине 1440 оптимальная картинка из имеющихся — 1400.jpg, на ширине 375 — 1000.jpg

1400.jpg
1000.jpg

Но если картинка занимает только половину, то 1400.jpg избыточно, достаточно 1000.jpg

1000.jpg
1000.jpg

Когда браузер знает, что картинка занимает половину, то правильно выбирает оптимальную картинку — 1000.jpg

Пока img с атрибутом srcset всегда занимает 100% ширины экрана, всё работает отлично, браузер знает ширину экрана и подберёт под неё оптимальную картинку из имеющихся в src и srcset.

<img 
  src="img-1000.jpg"
  srcset="img-1400.jpg 1400w">

Но допустим, картинка на широком экране занимает половину ширины:

img {
  width: 100%;
}

@media (width > 960px) {
  img {
    width: 50%;
  }
}
<img 
  src="img-1000.jpg"
  srcset="img-1400.jpg 1400w">

Браузер на момент выбора картинки не знает ширину img в вёрстке — выбор происходит до построения страницы. Поэтому и в этом случае он выберет картинку под всю ширину экрана.

Подскажем браузеру возможные размеры элемента в атрибуте sizes. В конце — размер по умолчанию, используется, если ни одно условие выше не удовлетворено.

img {
  width: 100%;
}

@media (width > 960px) {
  img {
    width: 50%;
  }
}
<!-- Проще всего взять медиавыражение из ЦСС, которое определяет ширину картинки, и перенести значения в sizes, заменяя проценты на vw -->

<img 
  src="img-1000.jpg"
  srcset="img-1400.jpg 1400w"
  sizes="
    (width > 960px) 50vw,
    100vw">

Теперь браузер знает, что картинка на компьютере занимает половину ширины и выберет оптимальную картинку. Если размеров много, следите за их порядком, браузер выберет первое сработавшее условие и не пойдёт дальше.

Адаптивные картинки, picture

Если атри­бут srcset под­ска­зы­вает бра­у­зеру, какую кар­тинку выбрать, то тег picture явно ука­зы­вает, какую кар­тинку выбрать. Обычно srcset исполь­зуют для опти­ми­за­ции, а picture — для работы с внеш­ним видом страницы.

Пока­жем раз­ные по фор­мату банеры на раз­ных раз­ме­рах окна:

<picture>
  <source 
    srcset="banner-wide.jpg"
    media="(width >= 1440px)"
  >
  <source 
    srcset="banner.jpg"
    media="(width > 768px)"
  >
  <!-- Если ни одно условие выше не выполняется -->
  <img src="banner-square.jpg">
</picture>

При ширине окна от 1440 пк

768⁠—1399 пк

При другой ширине

Адаптивные картинки, picture

Если атрибут srcset подсказывает браузеру, какую картинку выбрать, то тег picture явно указывает, какую картинку выбрать. Обычно srcset используют для оптимизации, а picture — для работы с внешним видом страницы.

Покажем разные по формату банеры на разных размерах окна:

<picture>
  <source 
    srcset="banner-wide.jpg"
    media="(width >= 1440px)"
  >
  <source 
    srcset="banner.jpg"
    media="(width > 768px)"
  >
  <!-- Если ни одно условие выше не выполняется -->
  <img src="banner-square.jpg">
</picture>

При ширине окна от 1440 пк

768⁠—1399 пк

768⁠—1399 пк

В про­дви­ну­той раз­ра­ботке созда­ние адап­тив­ных кар­ти­нок авто­ма­ти­зи­руют: про­грамма гене­ри­рует весь код и нуж­ные вер­сии самих картинок.

Руч­ное опи­са­ние и под­держка srcset или picture тре­бует много вре­мени и вни­ма­ния, боль­шой шанс что‑то упу­стить или оши­биться. Поэтому сове­туем при руч­ной вёрстке не стре­миться сде­лать все кар­тинки мак­си­мально адап­тив­ными, а огра­ни­читься точеч­ными опти­ми­за­ци­ями самой тяжё­лой графики.

Удоб­ный и эффек­тив­ный инстру­мент опти­ми­за­ции кар­ти­нок при руч­ной вёрстке — про­грамма ImageOptim. Она заметно умень­шает раз­мер кар­ти­нок без потери каче­ства и этим уско­ряет загрузку стра­ницы на всех устройствах.

Перетащите папку с вёрсткой внутрь программы, она сама найдёт и оптимизирует все картинки внутри папки

В продвинутой разработке создание адаптивных картинок автоматизируют: программа генерирует весь код и нужные версии самих картинок.

Ручное описание и поддержка srcset или picture требует много времени и внимания, большой шанс что‑то упустить или ошибиться. Поэтому советуем при ручной вёрстке не стремиться сделать все картинки максимально адаптивными, а ограничиться точечными оптимизациями самой тяжёлой графики.

Удобный и эффективный инструмент оптимизации картинок при ручной вёрстке — программа ImageOptim. Она заметно уменьшает размер картинок без потери качества и этим ускоряет загрузку страницы на всех устройствах.

Перетащите папку с вёрсткой внутрь программы, она сама найдёт и оптимизирует все картинки внутри папки

Тест

Какое ЦСС‑правило включит гриды для элемента?

position: grid;
display: grid;
display: grid-columns;
display: rows-cols;
grid-template-rows: auto;
grid-columns: 1fr 1fr;

Выберите верные утверждения о гридах.

Для грид‑контейнера задают ячейки, элементы внутри контейнера распределяются по этим ячейкам
Размеры колонок и строк задают только в относительных единицах измерения
Ячейки могут быть не фиксированными: тянуться или занимать всё доступное место
Гриды не имеют собственного свойства для управления отступами элементов

Внутри грид‑контейнера определяются направляющие, формируя колонки и строки, — ячейки, грид‑элементы распределяются по этим ячейкам. Размеры колонок и строк могут быть не строгими

Какой вид раскладки применится в приведённом коде и почему?

.box {
  display: flex;
  display: grid;
}

При конфликте в ЦСС применяются стили, объявленные ниже, кроме случаев, когда у стилей выше большая специфичность

Гриды, потому что это более современный вид раскладки

Гриды, потому что свойство с ними объявлено ниже

Флексбокс, потому что свойство объявлено выше

Флексбокс, потому что это наиболее поддерживаемый и распространённый вид раскладки

Не применятся ни гриды, ни флексбоксы, потому что произойдёт конфликт свойства

Применятся и гриды и флексбоксы по принципу каскада

За что отвечает свойство grid-auto-flow?

Свойство автоматически устанавливает остальные свойства грид‑контейнера в усреднённый вид, чтобы это не нужно было делать вручную
Свойство отвечает за порядок, в котором грид‑элементы заполняют грид‑контейнер: сначала по рядам или по колонкам
Свойство задаёт размеры ячеек, чья ширина задана как auto

По умолчанию элементы заполняют грид‑контейнер ряд за рядом, а если указать grid‑auto‑flow: column — начнут заполнять колонку за колонкой, сверху вниз

grid-template-columns: 1fr 1fr;
grid-template-columns: 2fr 2fr;
grid-template-columns: 1fr auto;
grid-template-columns: auto;
grid-template-columns: equal;
grid-template: equal;

Какой код заставит две грид‑колонки равномерно растянуться и занять всё доступное пространство?

Чтобы равномерно растянуть несколько колонок, укажите им одинаковые размеры в fr

Что рекомендуют авторы книги при выборе между флексбоксами и гридами?

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

Авторы советуют выбирать инструмент, более подходящий для каждой конкретной задачи

К чему может привести указание размеров элемента в пикселях?

Элемент не влезет в экран, если будет больше него по размеру

Содержимое элемента может переполнить его и обрезаться

Произойдёт синтаксическая ошибка и остановка применения дальнейших стилей

Элемент станет труднее переиспользовать

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

Как работает свойство overflow?

Свойство срабатывает, если содержимое элемента больше его ширины и высоты, и указывает, в какой другой элемент перенести не влезающее содержимое
Свойство определяет, как поступать с переполняющим элемент содержимым: обрезать, прятать или добавлять прокрутку
Свойство не допускает переполнения элемента, адаптируя его высоту и ширину под размеры содержимого
Свойство применяет оптимизации к содержимому элемента, чтобы пропорционально уменьшить его размеры и избежать переполнения

Что такое медиавыражение?

Медиавыражение — это указание предпочтительного формата отображения страницы, например без мобильной версии

Медиавыражение — это набор стилей, применяющиеся при соблюдении условий, связанных с форматом отображения, настройкой оформления или устройством пользователя

Медиавыражение — это особое представление страницы в виде превью для рассылок, социальных сетей и медиаплатформ

Ответьте на все вопросы теста,
чтобы узнать результат.
Результат
↺ Пересдать

Тест

Какое ЦСС‑правило включит гриды для элемента?

position: grid;
display: grid;
display: grid-columns;
display: rows-cols;
grid-template-rows: auto;
grid-columns: 1fr 1fr;

Выберите верные утверждения о гридах.

Для грид‑контейнера задают ячейки, элементы внутри контейнера распределяются по этим ячейкам
Размеры колонок и строк задают только в относительных единицах измерения
Ячейки могут быть не фиксированными: тянуться или занимать всё доступное место
Гриды не имеют собственного свойства для управления отступами элементов

Внутри грид‑контейнера определяются направляющие, формируя колонки и строки, — ячейки, грид‑элементы распределяются по этим ячейкам. Размеры колонок и строк могут быть не строгими

Какой вид раскладки применится в приведённом коде и почему?

.box {
  display: flex;
  display: grid;
}

При конфликте в ЦСС применяются стили, объявленные ниже, кроме случаев, когда у стилей выше большая специфичность

Гриды, потому что это более современный вид раскладки

Гриды, потому что свойство с ними объявлено ниже

Флексбокс, потому что свойство объявлено выше

Флексбокс, потому что это наиболее поддерживаемый и распространённый вид раскладки

Не применятся ни гриды, ни флексбоксы, потому что произойдёт конфликт свойства

Применятся и гриды и флексбоксы по принципу каскада

За что отвечает свойство grid-auto-flow?

Свойство автоматически устанавливает остальные свойства грид‑контейнера в усреднённый вид, чтобы это не нужно было делать вручную
Свойство отвечает за порядок, в котором грид‑элементы заполняют грид‑контейнер: сначала по рядам или по колонкам
Свойство задаёт размеры ячеек, чья ширина задана как auto

По умолчанию элементы заполняют грид‑контейнер ряд за рядом, а если указать grid‑auto‑flow: column — начнут заполнять колонку за колонкой, сверху вниз

grid-template-columns: 1fr 1fr;
grid-template-columns: 2fr 2fr;
grid-template-columns: 1fr auto;
grid-template-columns: auto;
grid-template-columns: equal;
grid-template: equal;

Какой код заставит две грид‑колонки равномерно растянуться и занять всё доступное пространство?

Чтобы равномерно растянуть несколько колонок, укажите им одинаковые размеры в fr

Что рекомендуют авторы книги при выборе между флексбоксами и гридами?

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

Авторы советуют выбирать инструмент, более подходящий для каждой конкретной задачи

К чему может привести указание размеров элемента в пикселях?

Элемент не влезет в экран, если будет больше него по размеру

Содержимое элемента может переполнить его и обрезаться

Произойдёт синтаксическая ошибка и остановка применения дальнейших стилей

Элемент станет труднее переиспользовать

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

Как работает свойство overflow?

Свойство срабатывает, если содержимое элемента больше его ширины и высоты, и указывает, в какой другой элемент перенести не влезающее содержимое
Свойство определяет, как поступать с переполняющим элемент содержимым: обрезать, прятать или добавлять прокрутку
Свойство не допускает переполнения элемента, адаптируя его высоту и ширину под размеры содержимого
Свойство применяет оптимизации к содержимому элемента, чтобы пропорционально уменьшить его размеры и избежать переполнения

Что такое медиавыражение?

Медиавыражение — это указание предпочтительного формата отображения страницы, например без мобильной версии

Медиавыражение — это набор стилей, применяющиеся при соблюдении условий, связанных с форматом отображения, настройкой оформления или устройством пользователя

Медиавыражение — это особое представление страницы в виде превью для рассылок, социальных сетей и медиаплатформ

Ответьте на все вопросы теста, чтобы узнать результат.
Результат
↺ Пересдать

4
Страницы

4
Страницы

Интерфейс конструкторов напоминает Фигму, а получившийся дизайн можно сразу опубликовать в интернете. Редимаг

Конструкторы сайтов

Кон­струк­тор — быст­рый спо­соб запу­стить сайт без напи­са­ния кода.

Стра­ницы соби­рают в визу­аль­ном редак­торе из типо­вых моду­лей, добав­ляют ани­ма­ции и спецэффекты.

Набор бес­плат­ных моду­лей и функ­ций обычно огра­ни­чен, поэтому боль­шин­ство сай­тов на кон­струк­то­рах похожи друг на друга. Хотите что‑то нестан­дарт­ное — при­дётся купить под­писку или про­явить сме­калку в усло­виях жёст­ких ограничений.

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

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

Интерфейс конструкторов напоминает Фигму, а получившийся дизайн можно сразу опубликовать в интернете. Редимаг

Конструкторы сайтов

Конструктор — быстрый способ запустить сайт без написания кода.

Страницы собирают в визуальном редакторе из типовых модулей, добавляют анимации и спецэффекты.

Набор бесплатных модулей и функций обычно ограничен, поэтому большинство сайтов на конструкторах похожи друг на друга. Хотите что‑то нестандартное — придётся купить подписку или проявить смекалку в условиях жёстких ограничений.

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

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

tilda.cc

Тильда

Тильда — кон­струк­тор, в кото­ром функ­ци­о­наль­ные сайты быстро соби­ра­ются из стан­дарт­ных бло­ков и шаб­ло­нов. Тильду легко осво­ить — это хоро­ший инстру­мент для типо­вых про­мо­стра­ниц и сайтов.

Тильда меньше подходит для «дизайнерских» сайтов — её шаблоны и блоки несколько топорные
Тильда

tilda.cc

Тильда меньше подходит для «дизайнерских» сайтов — её шаблоны и блоки несколько топорные
Тильда

Тильда

Тильда — конструктор, в котором функциональные сайты быстро собираются из стандартных блоков и шаблонов. Тильду легко освоить — это хороший инструмент для типовых промостраниц и сайтов.

Редимаг

Реди­маг — гораздо более «дизай­нер­ский» кон­струк­тор сай­тов, чем Тильда. Внеш­ний вид всех моду­лей сайта гибко настра­и­ва­ется, при этом интер­фейс кон­струк­тора акку­рат­ный, инту­и­тивно понят­ный, не пере­гру­жен­ный и не отвле­кает от работы над дизайном.

Но Реди­маг — доро­гое удо­воль­ствие. Годо­вая под­писка обой­дётся при­мерно в 500 дол­ла­ров, могут быть про­блемы с опла­той рос­сий­ской кар­той. Без плат­ной под­писки нельзя раз­ме­стить сайт на соб­ствен­ном домене и недо­ступны важ­ные биз­нес‑функ­ции вроде при­ёма заявок из форм и под­клю­че­ния систем аналитики.

readymag.com

Красивые сайты, сделанные в Редимаге
Readymag Examples

Редимаг

Редимаг — гораздо более «дизайнерский» конструктор сайтов, чем Тильда. Внешний вид всех модулей сайта гибко настраивается, при этом интерфейс конструктора аккуратный, интуитивно понятный, не перегруженный и не отвлекает от работы над дизайном.

Но Редимаг — дорогое удовольствие. Годовая подписка обойдётся примерно в 500 долларов, могут быть проблемы с оплатой российской картой. Без платной подписки нельзя разместить сайт на собственном домене и недоступны важные бизнес‑функции вроде приёма заявок из форм и подключения систем аналитики.

readymag.com

Красивые сайты, сделанные в Редимаге
Readymag Examples

webflow.com

Вебфлоу

Силь­ная сто­рона Веб­флоу — слож­ные биз­нес‑функ­ции, кастом­ные модули и инте­гра­ции с дру­гими сер­ви­сами и про­грам­мами. Можно син­хро­ни­зи­ро­вать товары на сайте с бух­гал­те­рией, под­клю­чить сер­вис поч­то­вых рас­сы­лок, настро­ить работу с соб­ствен­ным кодом на дру­гом сервере.

Веб­флоу — доро­гой и слож­ный инстру­мент. Исполь­зуйте его, если пони­ма­ете зачем, чтобы не пере­пла­тить и не уто­нуть в лиш­них функциях.

webflow.com

Вебфлоу

Сильная сторона Вебфлоу — сложные бизнес‑функции, кастомные модули и интеграции с другими сервисами и программами. Можно синхронизировать товары на сайте с бухгалтерией, подключить сервис почтовых рассылок, настроить работу с собственным кодом на другом сервере.

Вебфлоу — дорогой и сложный инструмент. Используйте его, если понимаете зачем, чтобы не переплатить и не утонуть в лишних функциях.

Готовые наборы стилей

Чтобы сэко­но­мить время на напи­са­нии сти­лей, раз­ра­бот­чики исполь­зуют гото­вые наборы — файлы с ЦСС‑клас­сами для реше­ния типо­вых задач.

Наборы отли­ча­ются содер­жи­мым: в лег­ко­вес­ном «Симпл‑грид» только классы для работы с модуль­ной сет­кой, а в огром­ном «Бут­страпе» есть стили на любой слу­чай жизни, свой набор ико­нок и даже опци­о­наль­ный пакет Яваскрипт‑примочек.

Эле­менты с клас­сами набора выгля­дят в еди­ном стиле и уже адап­ти­ро­ваны под раз­ные экраны.

Пре­иму­ще­ство набо­ров — это эко­но­мия вре­мени при вёрстке одно­об­раз­ных типо­вых стра­ниц, ведь не нужно каж­дый раз писать стили самостоятельно.

Недо­ста­ток набо­ров — это их уни­вер­саль­ность и усред­нён­ность. Наборы похожи друг на друга, как и сайты, сде­лан­ные с их помощью.

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

Набор стилей «Бутстрап»

Готовые наборы стилей

Чтобы сэкономить время на написании стилей, разработчики используют готовые наборы — файлы с ЦСС‑классами для решения типовых задач.

Наборы отличаются содержимым: в легковесном «Симпл‑грид» только классы для работы с модульной сеткой, а в огромном «Бутстрапе» есть стили на любой случай жизни, свой набор иконок и даже опциональный пакет Яваскрипт‑примочек.

Элементы с классами набора выглядят в едином стиле и уже адаптированы под разные экраны.

Преимущество наборов — это экономия времени при вёрстке однообразных типовых страниц, ведь не нужно каждый раз писать стили самостоятельно.

Недостаток наборов — это их универсальность и усреднённость. Наборы похожи друг на друга, как и сайты, сделанные с их помощью.

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

Набор стилей «Бутстрап»

Пора­бо­таем с гото­вым набо­ром сти­лей на при­мере Бутстрапа.

Сна­чала под­клю­чим файл сти­лей Бут­страпа к стра­нице. Проще всего под­клю­чать прямо с сер­ве­ров Бут­страпа. Доба­вим акту­аль­ную ссылку с сайта Бут­страпа в head:

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">

Или для надёж­но­сти ска­чаем файл по ссылке и будем дер­жать рядом с вёрсткой:

<link rel="stylesheet" href="./bootstrap.min.css">

Берём при­мер кода из доку­мен­та­ции Бут­страпа и убеж­да­емся, что стили под­клю­чи­лись и рабо­тают — на стра­нице появи­лась сти­ли­зо­ван­ная кнопка.

<button type="button" class="btn btn-primary">
  Inbox
  <span class="badge rounded-pill bg-danger">
    9+
  </span>
</button>

Кнопки. Документация Бутстрапа

Поработаем с готовым набором стилей на примере Бутстрапа.

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

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">

Или для надёжности скачаем файл по ссылке и будем держать рядом с вёрсткой:

<link rel="stylesheet" href="./bootstrap.min.css">

Берём пример кода из документации Бутстрапа и убеждаемся, что стили подключились и работают — на странице появилась стилизованная кнопка.

<button type="button" class="btn btn-primary">
  Inbox
  <span class="badge rounded-pill bg-danger">
    9+
  </span>
</button>

Кнопки. Документация Бутстрапа

8
4
2
10
6
4
2

Сетка в Бутстрапе

Клю­че­вая фишка Бут­страпа и дру­гих набо­ров сти­лей — гото­вые классы для работы с модуль­ной сеткой.

В Бут­страпе строки и ячейки сетки задают клас­сами row и col. Есть несколько вари­ан­тов работы с ними.

Пер­вый вари­ант — явно ука­зать ширину ячеек в колон­ках в рам­ках 12‑коло­ноч­ной сетки. Для кор­рект­ной работы в таком слу­чае, общая ширина ячеек в строке должна быть равна числу коло­нок — 12.

<div class="row">
  <div class="col-8"></div>
  <div class="col-4"></div>
</div>

<div class="row">
  <div class="col-2"></div>
  <div class="col-10"></div>
</div>

<div class="row">
  <div class="col-6"></div>
  <div class="col-4"></div>
  <div class="col-2"></div>
</div>

Вто­рой вари­ант — не ука­зы­вать ширину ячеек. Тогда они поде­лят доступ­ную ширину рав­но­мерно. Коли­че­ство таких ячеек в строке огра­ни­чено лишь здра­вым смыслом.

<div class="row">
  <div class="col"></div>
  <div class="col"></div>
</div>

<div class="row">
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
</div>

<div class="row">
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
</div>

Ячейки с ука­зан­ной шири­ной и без можно ком­би­ни­ро­вать, но только если вы хорошо пони­ма­ете, зачем это дела­ете и как пове­дёт себя ито­го­вая вёрстка. Разо­браться с пове­де­нием клас­сов Бут­страпа в этом и дру­гих нестан­дарт­ных слу­чаях помо­жет тща­тель­ное изу­че­ние документации.

Модульная сетка. Документация Бутстрапа

8
4
2
10
6
4
2

Сетка в Бутстрапе

Ключевая фишка Бутстрапа и других наборов стилей — готовые классы для работы с модульной сеткой.

В Бутстрапе строки и ячейки сетки задают классами row и col. Есть несколько вариантов работы с ними.

Первый вариант — явно указать ширину ячеек в колонках в рамках 12‑колоночной сетки. Для корректной работы в таком случае, общая ширина ячеек в строке должна быть равна числу колонок — 12.

<div class="row">
  <div class="col-8"></div>
  <div class="col-4"></div>
</div>

<div class="row">
  <div class="col-2"></div>
  <div class="col-10"></div>
</div>

<div class="row">
  <div class="col-6"></div>
  <div class="col-4"></div>
  <div class="col-2"></div>
</div>

Второй вариант — не указывать ширину ячеек. Тогда они поделят доступную ширину равномерно. Количество таких ячеек в строке ограничено лишь здравым смыслом.

<div class="row">
  <div class="col"></div>
  <div class="col"></div>
</div>

<div class="row">
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
</div>

<div class="row">
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
</div>

Ячейки с указанной шириной и без можно комбинировать, но только если вы хорошо понимаете, зачем это делаете и как поведёт себя итоговая вёрстка. Разобраться с поведением классов Бутстрапа в этом и других нестандартных случаях поможет тщательное изучение документации.

Модульная сетка. Документация Бутстрапа

Отступы в Бутстрапе

Отдель­ные классы в Бут­страпе управ­ляют падин­гами и мар­жи­нами — внут­рен­ними и внеш­ними отсту­пами эле­мента. Есть пять стан­дарт­ных раз­ме­ров отступов.

Класс p-1 уста­но­вит мини­маль­ный, а p-5 — мак­си­маль­ный воз­мож­ный падинг со всех четы­рёх сторон.

Классы с уточ­не­нием t, b, l, r (top, bottom, left, right) уста­но­вят падинг только с одной сто­роны, а с уточ­не­нием x или y — слева‑справа или сверху‑снизу.

<!-- Со всех сторон -->
<div class="p-1"></div>

<!-- Только сверху -->
<div class="pt-3"></div>

<!-- Сверху и снизу -->
<div class="pt-1 pb-2"></div>

<!-- Сверху и снизу -->
<div class="py-2"></div>

<!-- Сверху, снизу, слева и справа -->
<div class="pt-1 pb-2 px-2"></div>

Мар­жины зада­ются тем же обра­зом, но p меня­ется на m, а кроме зна­че­ний 1…5 доступно auto — отжать эле­мент до предела.

<div class="m-1"></div>

<div class="ml-2"></div>

<div class="ml-auto mt-4"></div>

Класcы падин­гов и мар­жи­нов можно комбинировать:

<div class="p-1 m-1"></div>

<div class="p-1 pl-3 ml-2"></div>

<div class="p-2 ml-auto mt-4"></div>

Отступы:

p‑1
pt‑3
pt‑1 pb‑2
py‑2
pt‑1 pb‑2 px‑2
m‑1
ml‑2
ml‑auto mt‑4
p‑1 m‑1
p‑1 pl‑3 ml‑2
p‑2 ml‑auto mt‑4
p‑1
pt‑3
pt‑1 pb‑2
py‑2
pt‑1 pb‑2 px‑2
m‑1
ml‑2
ml‑auto mt‑4
p‑1 m‑1
p‑1 pl‑3 ml‑2
p‑2 ml‑auto mt‑4

Отступы в Бутстрапе

Отдельные классы в Бутстрапе управляют падингами и маржинами — внутренними и внешними отступами элемента. Есть пять стандартных размеров отступов.

Класс p-1 установит минимальный, а p-5 — максимальный возможный падинг со всех четырёх сторон.

Классы с уточнением t, b, l, r (top, bottom, left, right) установят падинг только с одной стороны, а с уточнением x или y — слева‑справа или сверху‑снизу.

<!-- Со всех сторон -->
<div class="p-1"></div>

<!-- Только сверху -->
<div class="pt-3"></div>

<!-- Сверху и снизу -->
<div class="pt-1 pb-2"></div>

<!-- Сверху и снизу -->
<div class="py-2"></div>

<!-- Сверху, снизу, слева и справа -->
<div class="pt-1 pb-2 px-2"></div>

Маржины задаются тем же образом, но p меняется на m, а кроме значений 1…5 доступно auto — отжать элемент до предела.

<div class="m-1"></div>

<div class="ml-2"></div>

<div class="ml-auto mt-4"></div>

Класcы падингов и маржинов можно комбинировать:

<div class="p-1 m-1"></div>

<div class="p-1 pl-3 ml-2"></div>

<div class="p-2 ml-auto mt-4"></div>

Отступы:

Модификаторы ширины экрана в Бутстрапе

Обыч­ные классы Бут­страпа для работы с сет­кой и отсту­пами при­ме­нятся на всех раз­ме­рах экрана. Код из при­мера пока­жет ячейки оди­на­ко­вой ширины и на теле­фоне, и на компьютере:

<div class="row">
  <div class="col-6"></div>
  <div class="col-6"></div>
</div>

Но если допи­сать к классу осо­бый моди­фи­ка­тор, то класс нач­нёт рабо­тать только начи­ная с опре­де­лён­ной ширины экрана.

Напри­мер, моди­фи­ка­тор lg (large) при­ме­нит класс только на ширине экрана от 992 пк. Доба­вив такой класс, можно поста­вить ячейки друг над дру­гом на узком экране и в ряд на широком:

<div class="row">
  <div class="col-12 col-lg-6"></div>
  <div class="col-12 col-lg-6"></div>
</div>

В Бут­страпе есть пять моди­фи­ка­то­ров экрана: sm, md, lg, xl, xxl. Они при­ме­нят класс от ширины 576, 768, 992, 1200 и 1400 пк соответственно.

По умол­ча­нию все­гда при­ме­нятся классы без моди­фи­ка­тора, а классы с моди­фи­ка­то­ром пере­би­вают их на своей ширине экрана. Напри­мер, такой код изме­нит внут­рен­ний отступ от мини­маль­ного на теле­фоне до мак­си­маль­ного на экране шири­ной 1200⁠—1399 пк и снова на мини­маль­ный, начи­ная с ширины 1400 пк:

<div class="p-1 p-md-3 p-xl-5 p-xxl-1"></div>

Не все классы Бут­страпа под­дер­жи­вают моди­фи­ка­торы. Про­ве­рить под­держку можно экс­пе­ри­мен­таль­ным путём или загля­нув в доку­мен­та­цию инте­ре­су­ю­щего класса.

Модификаторы ширины экрана в Бутстрапе

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

<div class="row">
  <div class="col-6"></div>
  <div class="col-6"></div>
</div>

Но если дописать к классу особый модификатор, то класс начнёт работать только начиная с определённой ширины экрана.

Например, модификатор lg (large) применит класс только на ширине экрана от 992 пк. Добавив такой класс, можно поставить ячейки друг над другом на узком экране и в ряд на широком:

<div class="row">
  <div class="col-12 col-lg-6"></div>
  <div class="col-12 col-lg-6"></div>
</div>

В Бутстрапе есть пять модификаторов экрана: sm, md, lg, xl, xxl. Они применят класс от ширины 576, 768, 992, 1200 и 1400 пк соответственно.

По умолчанию всегда применятся классы без модификатора, а классы с модификатором перебивают их на своей ширине экрана. Например, такой код изменит внутренний отступ от минимального на телефоне до максимального на экране шириной 1200⁠—1399 пк и снова на минимальный, начиная с ширины 1400 пк:

<div class="p-1 p-md-3 p-xl-5 p-xxl-1"></div>

Не все классы Бутстрапа поддерживают модификаторы. Проверить поддержку можно экспериментальным путём или заглянув в документацию интересующего класса.

Гото­вый набор сти­лей — не все­гда хоро­ший выбор. Не cтоит без­думно тащить его в любую вёрстку.

Изу­чайте доку­мен­та­цию набора, чтобы пони­мать его воз­мож­но­сти и огра­ни­че­ния, когда он при­не­сёт пользу, а когда ста­нет обу­зой. Выби­райте инстру­мент под задачу, а не под­го­няйте задачу под инструмент.

Нестан­дарт­ный дизайн часто эффек­тив­нее вер­стать вруч­ную, не заго­няя себя в рамки гото­вых реше­ний. Руч­ная вёрстка слож­нее, но гораздо гибче. Об этом — сле­ду­ю­щие главы.

Любые готовые решения следует изучать и применять осмысленно

Готовый набор стилей — не всегда хороший выбор. Не cтоит бездумно тащить его в любую вёрстку.

Изучайте документацию набора, чтобы понимать его возможности и ограничения, когда он принесёт пользу, а когда станет обузой. Выбирайте инструмент под задачу, а не подгоняйте задачу под инструмент.

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

Любые готовые решения следует изучать и применять осмысленно

Принципы вёрстки страниц

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

От общего к частному

Вер­стайте от общего к част­ному, чтобы видеть над­си­стему и связи моду­лей, выби­рать опти­маль­ные реше­ния и меньше повто­ряться в коде. ХТМЛ и ЦСС зато­чены под этот прин­цип: теги вкла­ды­ва­ются, стили наследуются.

Сле­дуйте методу про­грес­сив­ного джи­пега: стра­ница все­гда готова к пуб­ли­ка­ции. Уве­ли­чи­ва­ется сте­пень про­ра­бо­тан­но­сти, а общий вид все­гда ясен.

Не переусложнять

Чем больше кода, тем легче оши­биться. Избе­гайте избы­точ­ной вло­жен­но­сти, выно­сите повто­ря­ю­щи­еся стили в классы, исполь­зуйте насле­до­ва­ние. Стре­ми­тесь делать код проще, насколько воз­можно, пока это не вре­дит функ­ци­о­наль­но­сти и не про­ти­во­ре­чит здра­вому смыслу.

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

Каждой задаче — свой инструмент

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

Следовать системе

Оди­на­ково оформ­лен­ный и пред­ска­зу­е­мый код быст­рее читать, понятно, как доба­вить эле­менты и изме­нить суще­ству­ю­щие. Это осо­бенно полезно при команд­ной работе.

Сле­дуйте еди­ным пра­ви­лам во всём коде. Назы­ва­ете классы через под­чёр­ки­ва­ние? Назы­вайте так все­гда и везде. Хра­ните стили для мобиль­ных в отдель­ном файле? Не пишите их в дру­гих местах.

Если в сере­дине пути поняли, что система не самая удач­ная — не спе­шите всё пере­де­лы­вать, обычно это того не стоит. Сде­лайте заметку на буду­щее и в сле­ду­ю­щий раз выбе­рите более удач­ную систему.

Плохой старт

<header> <nav class="nav"> <a href="…">Главная</a> <a href="…">Каталог</a> <button type="button" class="btnCart"> Корзина <span class="counter">9+</span> </button> </nav> </header>

Хороший старт

<header> <!-- … --> </header> <body> <!-- … --> <body> <footer> <!-- … --> <footer>

Избыточно

<nav class="nav"> <li class="nav__item"> <a class="nav__itemLink" href="…">Главная</a> </li> <li class="nav__item"> <a class="nav__itemLink" href="…">Каталог</a> </li> <li class="nav__item"> <a class="nav__itemLink" href="…">Контакты</a> </li> </ul> </nav>

Нормально

<nav сlass="nav"> <a href="…">Главная</a> <a href="…">Каталог</a> <a href="…">Контакты</a> </nav>

Неправильно

<p class="headingLarge">Ближайшие мероприятия</p> <div class="button">Купить</div> <p><u>Все статьи автора</u></a>

Правильно

<h1>Ближайшие мероприятия</h1> <button type="button" class="button">Купить</button> <a href="…">Все статьи автора</a>

Непредсказуемо

<a class="item promoItem"> <p class="item-title">…</p> <p class="itemprice">…</p> </a> <div class="feature is-highlighted"> <h3 class="featureTitle">…</h3> <div class="featureText">…</div> </div>

Предсказуемо

<a class="item is__promo"> <p class="item__title">…</p> <p class="item__price">…</p> </a> <div class="feature is__highlighted"> <h3 class="feature__title">…</h3> <div class="feature__text">…</div> </div>

Плохой старт

<header> <nav class="nav"> <a href="…">Главная</a> <a href="…">Каталог</a> <button type="button" class="btnCart"> Корзина <span class="counter">9+</span> </button> </nav> </header>

Хороший старт

<header> <!-- … --> </header> <body> <!-- … --> <body> <footer> <!-- … --> <footer>

Избыточно

<nav class="nav"> <li class="nav__item"> <a class="nav__itemLink" href="…">Главная</a> </li> <li class="nav__item"> <a class="nav__itemLink" href="…">Каталог</a> </li> <li class="nav__item"> <a class="nav__itemLink" href="…">Контакты</a> </li> </ul> </nav>

Нормально

<nav сlass="nav"> <a href="…">Главная</a> <a href="…">Каталог</a> <a href="…">Контакты</a> </nav>

Неправильно

<p class="headingLarge">Ближайшие мероприятия</p> <div class="button">Купить</div> <p><u>Все статьи автора</u></a>

Правильно

<h1>Ближайшие мероприятия</h1> <button type="button" class="button">Купить</button> <a href="…">Все статьи автора</a>

Непредсказуемо

<a class="item promoItem"> <p class="item-title">…</p> <p class="itemprice">…</p> </a> <div class="feature is-highlighted"> <h3 class="featureTitle">…</h3> <div class="featureText">…</div> </div>

Предсказуемо

<a class="item is__promo"> <p class="item__title">…</p> <p class="item__price">…</p> </a> <div class="feature is__highlighted"> <h3 class="feature__title">…</h3> <div class="feature__text">…</div> </div>

Принципы вёрстки страниц

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

От общего к частному

Верстайте от общего к частному, чтобы видеть надсистему и связи модулей, выбирать оптимальные решения и меньше повторяться в коде. ХТМЛ и ЦСС заточены под этот принцип: теги вкладываются, стили наследуются.

Следуйте методу прогрессивного джипега: страница всегда готова к публикации. Увеличивается степень проработанности, а общий вид всегда ясен.

Не переусложнять

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

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

Каждой задаче — свой инструмент

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

Следовать системе

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

Следуйте единым правилам во всём коде. Называете классы через подчёркивание? Называйте так всегда и везде. Храните стили для мобильных в отдельном файле? Не пишите их в других местах.

Если в середине пути поняли, что система не самая удачная — не спешите всё переделывать, обычно это того не стоит. Сделайте заметку на будущее и в следующий раз выберите более удачную систему.

Хром

Файерфокс

В Хроме и Файерфоксе разный интерлиньяж и буллиты

Портретная

Ландшафтная

При повороте основной текст стал крупнее заголовка

Хром

Сафари

В Хроме и Сафари разный кегль и шрифт в текстовой области

По умолчанию

Как хотим

Внутренние отступы и граница прибивались к 100% ширины инпутов

По умолчанию

Как хотим

Изображение котика не поместилось в окно и дало горизонтальную прокрутку

Начинать с чистого листа. В каж­дом бра­у­зере есть пред­уста­нов­лен­ные стили почти для всех эле­мен­тов. Они нужны, чтобы стра­ница без сти­лей была чита­е­мой, доступ­ной и адап­тив­ной. Дефолт­ные ЦСС‑пра­вила не стан­дар­ти­зо­ваны, поэтому они могут слегка отли­чаться от бра­у­зера к браузеру.

Кроме того, дефолт­ные стили ино­гда вре­дят вёрстке. Напри­мер, мобиль­ные бра­у­зеры при пово­роте теле­фона в ланд­шафт­ную ори­ен­та­цию уве­ли­чи­вает кегль тек­ста. При этом кегль основ­ного тек­ста может стать круп­нее кегля заго­лов­ков даже, если вы настро­или оба кегля.

Дефолт­ные стили местами довольно стран­ные. Напри­мер, кнопки и поля ввода не насле­дуют типо­гра­фи­че­ские пра­вила стра­ницы. Тек­сто­вая область исполь­зует систем­ный моно­ши­рин­ный шрифт, а тек­сто­вый инпут — систем­ный шрифт без засе­чек. И в обоих слу­чаях у них мик­ро­ско­пи­че­ский кегль: 13,333 пик­селя в Хроме, 11 пик­се­лей в Сафари.

Нако­нец, дефолт­ные стили тянут за собой «ино­пла­нет­ное» пове­де­ние из спе­ци­фи­ка­ции. Напри­мер, тек­сто­вые инпуты с width: 100% пере­пол­няют форму, потому что по умол­ча­нию внут­рен­ние отступы и гра­ницы не учи­ты­ва­ются в ширине. Изоб­ра­же­ния по умол­ча­нию не тянутся. Кар­тинка шири­ной в 800 пик­се­лей пере­пол­нит кон­тей­нер шири­ной в 500 пик­се­лей, а не попы­та­ется впи­саться в него.

Чтобы гаран­ти­ро­ванно изба­виться от побоч­ных эффек­тов дефолт­ных сти­лей бра­у­зера, нужно сна­чала обну­лить и нор­ма­ли­зо­вать их. А потом с чистого листа добав­лять свои стили. Так мы полу­чим пред­ска­зу­е­мое пове­де­ние вёрстки в любом браузере.

Хром

Файерфокс

В Хроме и Файерфоксе разный интерлиньяж и буллиты

Портретная

Ландшафтная

При повороте основной текст стал крупнее заголовка

Хром

Сафари

В Хроме и Сафари разный кегль и шрифт в текстовой области

По умолчанию

Как хотим

Внутренние отступы и граница прибивались к 100% ширины инпутов

По умолчанию

Как хотим

Изображение котика не поместилось в окно и дало горизонтальную прокрутку

Начинать с чистого листа. В каждом браузере есть предустановленные стили почти для всех элементов. Они нужны, чтобы страница без стилей была читаемой, доступной и адаптивной. Дефолтные ЦСС‑правила не стандартизованы, поэтому они могут слегка отличаться от браузера к браузеру.

Кроме того, дефолтные стили иногда вредят вёрстке. Например, мобильные браузеры при повороте телефона в ландшафтную ориентацию увеличивает кегль текста. При этом кегль основного текста может стать крупнее кегля заголовков даже, если вы настроили оба кегля.

Дефолтные стили местами довольно странные. Например, кнопки и поля ввода не наследуют типографические правила страницы. Текстовая область использует системный моноширинный шрифт, а текстовый инпут — системный шрифт без засечек. И в обоих случаях у них микроскопический кегль: 13,333 пикселя в Хроме, 11 пикселей в Сафари.

Наконец, дефолтные стили тянут за собой «инопланетное» поведение из спецификации. Например, текстовые инпуты с width: 100% переполняют форму, потому что по умолчанию внутренние отступы и границы не учитываются в ширине. Изображения по умолчанию не тянутся. Картинка шириной в 800 пикселей переполнит контейнер шириной в 500 пикселей, а не попытается вписаться в него.

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

Сове­туем перед нача­лом вёрстки сбро­сить стан­дарт­ные стили, обну­лить отступы, нор­ма­ли­зо­вать пове­де­ние по умол­ча­нию, изба­виться от неоче­вид­ных свойств. Такие стили назы­вают ресе­том (англ. reset — «сброс»). Ресет дела­ется добав­ле­нием осо­бого кода в начало файла со сти­лями. При­ве­дём при­мер мини­маль­ного ресета и про­ком­мен­ти­руем реше­ния в нём.

Начи­наем с вол­шеб­ной фор­мулы для всех эле­мен­тов и псев­до­эле­мен­тов, застав­ля­ю­щей бра­у­зер учи­ты­вать внут­рен­ние поля и рамку в ширине и высоте элемента.

Про­сим бра­у­зер не уве­ли­чи­вать кегль при пово­роте теле­фона в ланд­шафт­ную ори­ен­та­цию. Зна­че­ния с пре­фик­сами нужны, потому что мобиль­ный Сафари под­дер­жи­вает только вари­ант с -webkit-, а Файр­фокс на Андро­иде — только вари­ант с -moz-.

Обну­ляем отступы по умол­ча­нию у тек­сто­вых эле­мен­тов. Отступы между эле­мен­тами лучше зада­вать не у самих эле­мен­тов, а у их комбинаций.

Заодно уби­раем бул­литы в спис­ках. Чаще всего эле­менты списка оформ­ляют втяжкой.

Нор­ма­ли­зуем пове­де­ние медиа‑эле­мен­тов. Делаем их блоч­ными и адаптивными.

Про­сим бра­у­зер уна­сле­до­вать настройки шрифта в кон­тро­лах от окру­жа­ю­щего тек­ста, чтобы они не были слиш­ком мелкими.

*, *::before, *::after {
  box-sizing: border-box;
}

html {
  -moz-text-size-adjust: none;
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
}

body, h1, h2, h3, h4, p, ul, ol, li,
figure, blockquote, dl, dd {
  margin: 0;
}

ul, ol {
  list-style: none;
}

img, picture, video, svg {
  display: block;
  max-width: 100%;
  height: auto;
}

input, textarea, select, button {
  font: inherit;
}
*, *::before, *::after {
  box-sizing: border-box;
}

html {
  -moz-text-size-adjust: none;
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
}

body, h1, h2, h3, h4, p, ul, ol, li,
figure, blockquote, dl, dd {
  margin: 0;
}

ul, ol {
  list-style: none;
}

img, picture, video, svg {
  display: block;
  max-width: 100%;
  height: auto;
}

input, textarea, select, button {
  font: inherit;
}

Советуем перед началом вёрстки сбросить стандартные стили, обнулить отступы, нормализовать поведение по умолчанию, избавиться от неочевидных свойств. Такие стили называют ресетом (англ. reset — «сброс»). Ресет делается добавлением особого кода в начало файла со стилями. Приведём пример минимального ресета и прокомментируем решения в нём.

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

Просим браузер не увеличивать кегль при повороте телефона в ландшафтную ориентацию. Значения с префиксами нужны, потому что мобильный Сафари поддерживает только вариант с -webkit-, а Файрфокс на Андроиде — только вариант с -moz-.

Обнуляем отступы по умолчанию у текстовых элементов. Отступы между элементами лучше задавать не у самих элементов, а у их комбинаций.

Заодно убираем буллиты в списках. Чаще всего элементы списка оформляют втяжкой.

Нормализуем поведение медиа‑элементов. Делаем их блочными и адаптивными.

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

xxx xx xxxxx xxxxx xxxxx xxx xx xxxxx xxxxx xxx xx xxxxx xxxxx xxxxx xxxxx x xxxx xxxxx xxx xx xxxxx xx xxx xxxxx xx xxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxx xx xxxxx xxxxx xx xxx xxxxx xxxxx xxxxx xxxxx xx xxx xxxxx x xxxx xxxxx
xxx xx xxxxx xxxxx xxxxx xxx xx xxxxx xxxxx xxx xx xxxxx xxxxx xxxxx xxxxx x xxxx xxxxx xxx xx xxxxx xx xxx xxxxx xx xxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxx xx xxxxx xxxxx xx xxx xxxxx xxxxx xxxxx xxxxx xx xxx xxxxx x xxxx xxxxx

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

Чтобы задать фор­мат, исполь­зуют обёртки, классы, зада­ю­щие мак­си­маль­ную ширину, поля и поло­же­ние содер­жи­мого на стра­нице. Фор­мат может быть рези­но­вым, фик­си­ро­ван­ным или комбинированным.

Самая про­стая «рези­но­вая» обёртка может выгля­деть так:

.layoutContainer {
  margin-left: 6%; /* Задаём правое и левое поле в 6% от ширины окна */
  margin-right: 6%;
}

«Фик­си­ро­ван­ная» обёртка жёстко задаёт ширину:

.layoutContainer {
  width: 960px;
  margin-left: auto; /* Центрируем с помощью автомаржинов */
  margin-right: auto;
  padding-left: 20px; /* Добавляем фиксированные поля в 20 пк, чтобы содержимое не прилипало к краям окон шириной менее 960 пк*/
  padding-right: 20px;
}

Их ком­би­на­ция, обёртка с огра­ни­че­нием мак­си­маль­ной ширины и рези­но­выми полями, обычно выгля­дит так:

.layoutContainer {
  max-width: 960px; /* Задаём максимальную ширину */
  margin-left: auto;
  margin-right: auto;
  padding-left: 20px;
  padding-right: 20px;
}

Обёрт­кой может быть общий кон­тей­нер, в кото­рый завёр­нуто всё содер­жи­мое стра­ницы, или класс, кото­рый добав­ляют «эта­жам» страницы.

Кон­тей­нер‑обёртка подой­дёт для слу­чаев, когда струк­тура содер­жи­мого стра­ниц оди­на­ко­вая и одно­тип­ная, напри­мер, для ста­тей в блоге.

Класс‑обёртка подой­дёт для слу­чаев, когда часть содер­жи­мого стра­ницы может не под­чи­няться огра­ни­че­ниям. Ска­жем, шапка и под­вал тянутся на всю ширину окна, а содер­жи­мое стра­ницы — огра­ни­чено по ширине.

xxx xx xxxxx xxxxx xxxxx xxx xx xxxxx xxxxx xxx xx xxxxx xxxxx xxxxx xxxxx x xxxx xxxxx xxx xx xxxxx xx xxx xxxxx xx xxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxx xx xxxxx xxxxx xx xxx xxxxx xxxxx xxxxx xxxxx xx xxx xxxxx x xxxx xxxxx
xxx xx xxxxx xxxxx xxxxx xxx xx xxxxx xxxxx xxx xx xxxxx xxxxx xxxxx xxxxx x xxxx xxxxx xxx xx xxxxx xx xxx xxxxx xx xxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxxxx xxx xx xxxxx xxxxx xx xxx xxxxx xxxxx xxxxx xxxxx xx xxx xxxxx x xxxx xxxxx

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

Чтобы задать формат, используют обёртки, классы, задающие максимальную ширину, поля и положение содержимого на странице. Формат может быть резиновым, фиксированным или комбинированным.

Самая простая «резиновая» обёртка может выглядеть так:

.layoutContainer {
  margin-left: 6%; /* Задаём правое и левое поле в 6% от ширины окна */
  margin-right: 6%;
}

«Фиксированная» обёртка жёстко задаёт ширину:

.layoutContainer {
  width: 960px;
  margin-left: auto; /* Центрируем с помощью автомаржинов */
  margin-right: auto;
  padding-left: 20px; /* Добавляем фиксированные поля в 20 пк, чтобы содержимое не прилипало к краям окон шириной менее 960 пк*/
  padding-right: 20px;
}

Их комбинация, обёртка с ограничением максимальной ширины и резиновыми полями, обычно выглядит так:

.layoutContainer {
  max-width: 960px; /* Задаём максимальную ширину */
  margin-left: auto;
  margin-right: auto;
  padding-left: 20px;
  padding-right: 20px;
}

Обёрткой может быть общий контейнер, в который завёрнуто всё содержимое страницы, или класс, который добавляют «этажам» страницы.

Контейнер‑обёртка подойдёт для случаев, когда структура содержимого страниц одинаковая и однотипная, например, для статей в блоге.

Класс‑обёртка подойдёт для случаев, когда часть содержимого страницы может не подчиняться ограничениям. Скажем, шапка и подвал тянутся на всю ширину окна, а содержимое страницы — ограничено по ширине.

Точки излома

Ваш дизайн может рабо­тать не на всех раз­ме­рах экрана. На мобиль­ном теле­фоне может не поме­ститься меню или некра­сиво пере­но­ситься текст. Чтобы адап­ти­ро­вать стра­ницу к мобиль­ному теле­фону и дру­гим устрой­ствам, вве­дём поня­тие «точек излома».

Точка излома, breakpoint, — ширина экрана, при кото­рой одна вер­сия вёрстки сме­ня­ется на дру­гую. Напри­мер, при ширине экрана менее 991 пик­селя меню могло бы занять всю ширину экрана и стать про­кру­чи­ва­е­мым. Исполь­зуем для этого медиавыражения.

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

В боль­шин­стве попу­ляр­ных ЦСС‑фрейм­вор­ков эти точки выгля­дят так:

Узкие мобильные< 576 пк
Мобильные< 768 пк
Лаптопы≥ 768 пк
Десктопы≥ 992 пк
Широ­кие десктопы≥ 1400 пк

Вы можете исполь­зо­вать любые дру­гие зна­че­ния, опи­ра­ясь на ста­ти­стику экра­нов посе­ти­те­лей сайта или на зна­че­ния, задан­ные в вашем ЦСС‑фреймворке.

Напри­мер, на сайте бюро мы оста­но­ви­лись на трёх типах устройств:

Мобильные≤ 960 пк
Лаптопы> 960 пк
Десктопы> 1400 пк

Сове­туем исполь­зо­вать как можно меньше устройств и точек излома. Чем их больше, тем больше вари­ан­тов рас­кладки нужно при­ду­мать, свер­стать и про­ве­рить. Если шапка и под­вал адап­ти­ру­ются к двум устрой­ствам, это даёт 2×2 = 4 вари­анта рас­кладки. Если шапка и под­вал адап­ти­ру­ются к пяти устрой­ствам: это даёт 2×5 = 10 вари­ан­тов раскладки.

Макбук, 1440×900

Айпад, 820×1180

Айфон, 393×852

Макбук, 1440×900

Айпад, 820×1180

Айфон, 393×852

Точки излома

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

Точка излома, breakpoint, — ширина экрана, при которой одна версия вёрстки сменяется на другую. Например, при ширине экрана менее 991 пикселя меню могло бы занять всю ширину экрана и стать прокручиваемым. Используем для этого медиавыражения.

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

В большинстве популярных ЦСС‑фреймворков эти точки выглядят так:

Узкие мобильные< 576 пк
Мобильные< 768 пк
Лаптопы≥ 768 пк
Десктопы≥ 992 пк
Широкие десктопы≥ 1400 пк

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

Например, на сайте бюро мы остановились на трёх типах устройств:

Мобильные≤ 960 пк
Лаптопы> 960 пк
Десктопы> 1400 пк

Советуем использовать как можно меньше устройств и точек излома. Чем их больше, тем больше вариантов раскладки нужно придумать, сверстать и проверить. Если шапка и подвал адаптируются к двум устройствам, это даёт 2×2 = 4 варианта раскладки. Если шапка и подвал адаптируются к пяти устройствам: это даёт 2×5 = 10 вариантов раскладки.

Большая. Новодмитровская улица., 36, строение. 2
Москва, Россия, 127015

Адап­та­ция может пона­до­биться не только на уровне всего сайта, но и на уровне отдель­ных эле­мен­тов. Напри­мер, адрес ком­па­нии в под­вале может посте­пенно сокра­щать свой текст, когда пере­стаёт помещаться:

Боль­шая Новод­мит­ров­ская улица, 36, стро­е­ние 2
Боль­шая Новод­мит­ров­ская улица, 36, стр. 2
Б. Новод­мит­ров­ская ул., 36, стр. 2

В таких слу­чаях точки излома лучше выби­рать исходя из содер­жи­мого и исполь­зо­вать Container Queries — медиа­вы­ра­же­ния, оттал­ки­ва­ю­щи­еся от раз­ме­ров кон­тей­нера, а не от раз­ме­ров окна:

<footer>
  <div class="address">
    Б<span class="sacrifice--second">ольшая</span><span>.</span> Новодмитровская ул<span class="sacrifice--second">ица</span><span>.</span>,
    36, стр<span class="sacrifice--first">оение</span><span>.</span> 2
  </div>
</footer>

Для логичных переносов склеили неразрывными пробелами части адреса, сильно связанные друг с другом по смыслу: «Большая Новодмитровская улица», «36, строение 2»

.address {
  container-type: inline-size; /* Объявляем .address контейнером, с шириной которого можно делать медиавыражения */
}

.address .sacrifice--first + span,
.address .sacrifice--first + span {
  display: none; /* Скрываем спаны с точками, пока они не нужны */
}

@container (width <= 21em) { /* При ширине подвала менее 21em, используем первую волну сокращений */
  .address .sacrifice--first {
    display: none;
  }

  .address .sacrifice--first + span {
    display: inline;
  }
}

@container (width <= 19em) { /* При ширине подвала менее 19em, используем вторую волну сокращений */
  .address .sacrifice--second {
    display: none;
  }

  .address .sacrifice--second + span {
    display: inline;
  }
}

Конечно, можно было бы исполь­зо­вать и обыч­ные медиа­вы­ра­же­ния с шири­ной экрана. Но это сде­лало бы вёрстку адреса более хруп­кой и менее пере­но­си­мой. Если в под­вале у адреса появится сосед, адрес ста­нет зани­мать меньше места. Текст при­дётся сокра­щать раньше, зна­чит, при­дётся пере­счи­ты­вать точки в медиавыражениях.

Большая. Новодмитровская улица., 36, строение. 2
Москва, Россия, 127015

Адаптация может понадобиться не только на уровне всего сайта, но и на уровне отдельных элементов. Например, адрес компании в подвале может постепенно сокращать свой текст, когда перестаёт помещаться:

Большая Новодмитровская улица, 36, строение 2
Большая Новодмитровская улица, 36, стр. 2
Б. Новодмитровская ул., 36, стр. 2

В таких случаях точки излома лучше выбирать исходя из содержимого и использовать Container Queries — медиавыражения, отталкивающиеся от размеров контейнера, а не от размеров окна:

<footer>
  <div class="address">
    Б<span class="sacrifice--second">ольшая</span><span>.</span> Новодмитровская ул<span class="sacrifice--second">ица</span><span>.</span>,
    36, стр<span class="sacrifice--first">оение</span><span>.</span> 2
  </div>
</footer>

Для логичных переносов склеили неразрывными пробелами части адреса, сильно связанные друг с другом по смыслу: «Большая Новодмитровская улица», «36, строение 2»

.address {
  container-type: inline-size; /* Объявляем .address контейнером, с шириной которого можно делать медиавыражения */
}

.address .sacrifice--first + span,
.address .sacrifice--first + span {
  display: none; /* Скрываем спаны с точками, пока они не нужны */
}

@container (width <= 21em) { /* При ширине подвала менее 21em, используем первую волну сокращений */
  .address .sacrifice--first {
    display: none;
  }

  .address .sacrifice--first + span {
    display: inline;
  }
}

@container (width <= 19em) { /* При ширине подвала менее 19em, используем вторую волну сокращений */
  .address .sacrifice--second {
    display: none;
  }

  .address .sacrifice--second + span {
    display: inline;
  }
}

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

Шапки, меню, подвалы

При­ве­дём при­меры вёрстки типич­ных шапок, меню и под­ва­лов сай­тов. Сосре­до­то­чимся только на рас­кладке эле­мен­тов, оста­вив типо­гра­фику на усмот­ре­ние читателю.

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

Шапки, меню, подвалы

Приведём примеры вёрстки типичных шапок, меню и подвалов сайтов. Сосредоточимся только на раскладке элементов, оставив типографику на усмотрение читателю.

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

Типичная шапка из трёх элементов: логотипа, меню и телефона

В контейнере <nav> — navigation — собирают ссылки для навигации по сайту

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

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

Мы используем max‑content, а не min‑content, чтобы пункты меню из нескольких слов не переносились

Аналогично можно было бы выделить текущий пункт меню начертанием или цветом

По умолчанию содержимое грид‑ячеек выравнивается по верхнему краю

В гридах align‑items управляет выравниванием по вертикали, а justify‑items — по горизонтали

В гридах justify‑self управляет выравниванием внутри ячейки

Иногда подчёркивание делают с помощью border‑bottom, background‑image или box‑shadow

Свер­стаем про­стую шапку «в линию», встре­ча­ю­щу­юся на боль­шин­стве сай­тов. Сосре­до­то­чимся только на рас­кладке, опу­стив типо­гра­фику и стили для сброса.

Нач­нём с раз­метки. Собе­рём шапку в кон­тей­нере <header>. Для лого­типа возь­мём див с кар­тин­кой, завёр­ну­той в ссылку на глав­ную стра­ницу. Меню собе­рём в нену­ме­ро­ван­ный спи­сок <ul>. Теле­фон сде­лаем кли­ка­бель­ной ссыл­кой с хит­рым про­то­ко­лом tel:

<header class="header">
  <div class="logo">
    <a href="/">
      <img src="logo.svg">
    </a>
  </div>

  <nav class="nav">
    <ul>
      <li class="nav__item">
        <a href="/products/">Продукция</a>
      </li>
      <li class="nav__item">
        <a href="/services/">Услуги</a>
      </li>
      <li class="nav__item is__active">
        <a href="/blog/">Блог</a>
      </li>
      <li class="nav__item">
        <a href="/contacts/">Контакты</a>
      </li>
    </ul>
  </nav>

  <div class="tel">
    <a href="tel:+74954005050">
      +7 495 400‑50‑50
    </a>
  </div>
</header>

Стилизуем логотип. Зафик­си­руем его раз­меры и све­сим кру­жок лого­типа за линии сетки:

.logo {
  width: 40px;
  height: 40px;
  margin: 0 -5px;
}

Стилизуем меню. Вклю­чим гриды и раз­ло­жим пункты меню по колонкам:

.nav ul {
  display: grid;
  grid-auto-flow: column;
}

Опре­де­лим авто­ма­ти­че­ские колонки по мак­си­маль­ному содер­жи­мому и зада­дим отступ между пунк­тами меню:

.nav ul {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  column-gap: 20px;
}

Стилизуем пункты меню. Уве­ли­чим кли­ка­бель­ную область ссы­лок и сти­ли­зуем теку­щий пункт меню:

.nav ul {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  column-gap: 20px;
  align-items: center;
}

.nav__item a {
  position: relative;
  z-index: 1;
}

.nav__item a::before {
  content: '';
  position: absolute;
  inset: -3px -7px;
  z-index: -1;
}

.nav__item.is__active a {
  color: #fff;
  text-decoration: none;
}

.nav__item.is__active a::before {
  background-color: #ff5722;
  border-radius: 8px;
}

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

.header {
  display: grid;
  grid-template-columns: min-content max-content 1fr;
  column-gap: 20px;
  padding-top: 10px;
}
<header class="header layoutContainer">
  <!-- … -->
</header>

Отцен­три­руем всё по вертикали:

.header {
  display: grid;
  grid-template-columns: min-content max-content 1fr;
  column-gap: 20px;
  padding-top: 10px;
  align-items: center;
  line-height: 1;
}

Ото­жмём теле­фон к пра­вому краю:

.header {
  display: grid;
  grid-template-columns: min-content max-content 1fr;
  column-gap: 20px;
  padding: 10px;
  align-items: center;
}

.header .tel {
  justify-self: end;
}

Убе­рём под­чер­ки­ва­ния у ссы­лок в шапке:

.header {
  display: grid;
  grid-template-columns: min-content max-content 1fr;
  column-gap: 20px;
  padding: 10px;
  align-items: center;
}

.header .tel {
  justify-self: end;
}

.header a {
  text-decoration: none;
}

Типичная шапка из трёх элементов: логотипа, меню и телефона

В контейнере <nav> — navigation — собирают ссылки для навигации по сайту

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

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

Мы используем max‑content, а не min‑content, чтобы пункты меню из нескольких слов не переносились

Аналогично можно было бы выделить текущий пункт меню начертанием или цветом

По умолчанию содержимое грид‑ячеек выравнивается по верхнему краю

В гридах align‑items управляет выравниванием по вертикали, а justify‑items — по горизонтали

В гридах justify‑self управляет выравниванием внутри ячейки

Иногда подчёркивание делают с помощью border‑bottom, background‑image или box‑shadow

Сверстаем простую шапку «в линию», встречающуюся на большинстве сайтов. Сосредоточимся только на раскладке, опустив типографику и стили для сброса.

Начнём с разметки. Соберём шапку в контейнере <header>. Для логотипа возьмём див с картинкой, завёрнутой в ссылку на главную страницу. Меню соберём в ненумерованный список <ul>. Телефон сделаем кликабельной ссылкой с хитрым протоколом tel:

<header class="header">
  <div class="logo">
    <a href="/">
      <img src="logo.svg">
    </a>
  </div>

  <nav class="nav">
    <ul>
      <li class="nav__item">
        <a href="/products/">Продукция</a>
      </li>
      <li class="nav__item">
        <a href="/services/">Услуги</a>
      </li>
      <li class="nav__item is__active">
        <a href="/blog/">Блог</a>
      </li>
      <li class="nav__item">
        <a href="/contacts/">Контакты</a>
      </li>
    </ul>
  </nav>

  <div class="tel">
    <a href="tel:+74954005050">
      +7 495 400‑50‑50
    </a>
  </div>
</header>

Стилизуем логотип. Зафиксируем его размеры и свесим кружок логотипа за линии сетки:

.logo {
  width: 40px;
  height: 40px;
  margin: 0 -5px;
}

Стилизуем меню. Включим гриды и разложим пункты меню по колонкам:

.nav ul {
  display: grid;
  grid-auto-flow: column;
}

Определим автоматические колонки по максимальному содержимому и зададим отступ между пунктами меню:

.nav ul {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  column-gap: 20px;
}

Стилизуем пункты меню. Увеличим кликабельную область ссылок и стилизуем текущий пункт меню:

.nav ul {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  column-gap: 20px;
  align-items: center;
}

.nav__item a {
  position: relative;
  z-index: 1;
}

.nav__item a::before {
  content: '';
  position: absolute;
  inset: -3px -7px;
  z-index: -1;
}

.nav__item.is__active a {
  color: #fff;
  text-decoration: none;
}

.nav__item.is__active a::before {
  background-color: #ff5722;
  border-radius: 8px;
}

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

.header {
  display: grid;
  grid-template-columns: min-content max-content 1fr;
  column-gap: 20px;
  padding-top: 10px;
}
<header class="header layoutContainer">
  <!-- … -->
</header>

Отцентрируем всё по вертикали:

.header {
  display: grid;
  grid-template-columns: min-content max-content 1fr;
  column-gap: 20px;
  padding-top: 10px;
  align-items: center;
  line-height: 1;
}

Отожмём телефон к правому краю:

.header {
  display: grid;
  grid-template-columns: min-content max-content 1fr;
  column-gap: 20px;
  padding: 10px;
  align-items: center;
}

.header .tel {
  justify-self: end;
}

Уберём подчеркивания у ссылок в шапке:

.header {
  display: grid;
  grid-template-columns: min-content max-content 1fr;
  column-gap: 20px;
  padding: 10px;
  align-items: center;
}

.header .tel {
  justify-self: end;
}

.header a {
  text-decoration: none;
}

Адап­ти­руем полу­чив­шу­юся шапку для мобиль­ных устройств. Будем счи­тать, что в нашем дизайне мобиль­ные устрой­ства — это устрой­ства с экра­ном шири­ной менее 992 пикселей.

Обер­нём <nav> в див с клас­сом navHolder — он при­го­дится нам для про­крутки и обре­за­ния меню:

<header class="header">
  <div class="logo">…</div>

  <div class="navHolder">
    <nav class="nav">
      <!-- … -->
    </nav>
  </div>

  <div class="tel">…</div>
</header>

Поме­няем рас­кладку шапки. Вме­сто трёх коло­нок сде­лаем сетку 2×2:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
}

Пере­ста­вим теле­фон в пра­вый верх­ний угол, меню поста­вим вниз и рас­тя­нем на две колонки:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
  }
}

Рас­тя­нем меню на всю ширину экрана:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
    margin-left: -10px;
    margin-right: -10px;
  }
}

Вклю­чим гори­зон­таль­ную про­крутку в меню:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
    margin-left: -10px;
    margin-right: -10px;
  }
  
  .nav {
    overflow-x: scroll; /* Зададим возможность горизонтальной прокрутки */
    -webkit-overflow-scrolling: touch; /* Для Сафари дополнительно зададим инерцию при прокрутке. С инерцией меню будет «оттягиваться» при попытке прокрутить меню за его пределы. */
  }
}

Из‑за overflow обре­за­лись поля у теку­щего пункта в меню. Плюс при про­крутке появ­ля­ется гори­зо­наль­ный скрол­бар. Устра­ним эти проблемы:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
    margin-left: -10px;
    margin-right: -10px;
  }
  
  .nav {
    padding: 10px; /* Добавим дополнительные поля */
    margin: -10px 0; /* С помощью отрицательных отступов сверху-снизу компенсируем «нарощенные» поля */
    scrollbar-width: none; /* Скроем скролбар в Хроме и Файрфоксе */
    overflow-x: scroll;
    -webkit-overflow-scrolling: touch;
  }

  .nav::-webkit-scrollbar {
    display: none; /* Скроем скролбар в Сафари */
  }
}

Доба­вим немного кос­ме­тики в виде «забе­ле­ния» по краям с помо­щью маски и градиента:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
    margin-left: -10px;
    margin-right: -10px;
  }

  .nav {
    padding: 10px;
    margin: -10px 0;
    scrollbar-width: none;
    overflow-x: scroll;
    -webkit-overflow-scrolling: touch;
    mask-image: linear-gradient(
      90deg,
      transparent 0,
      rgba(0, 0, 0, .25) 8px,
      #000 16px,
      #000 calc(100% - 16px),
      rgba(0, 0, 0, .25) calc(100% - 8px),
      transparent
    );
  }

  .nav::-webkit-scrollbar {
    display: none;
  }
}

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

@media (width < 992px) {
  .navHolder {
    position: relative; /* Мы используем обертку для линеек, потому что линейки у .nav попадали бы под маску и забеление */
  }

  .navHolder::before,
  .navHolder::after {
    content: ''; /* Используем псевдоэлементы, а не рамку, потому что их позицией можно управлять */
    position: absolute;
    left: 0;
    right: 0;
    height: 1px;
    background: rgba(0, 0, 0, .07);
  }

  .navHolder::before {
    top: -9px;
  }

  .navHolder::after {
    bottom: -8px;
  }
}

Оста­лась одна про­блема. При неко­то­рых шири­нах экрана меню обре­за­ется так, что кажется, что всё поме­сти­лось и про­кру­чи­вать нечего. Чтобы попра­вить это, можно напи­сать неболь­шой скрипт, кото­рый будет ана­ли­зи­ро­вать ширину эле­мен­тов и менять зна­че­ние column-gap в меню так, чтобы послед­ний види­мый эле­мент все­гда обрезался.

Если меню ста­тич­ное и вряд ли будет меняться, проще задать под­хо­дя­щие зна­че­ния column-gap для раз­ной ширины экрана.

Адаптируем получившуюся шапку для мобильных устройств. Будем считать, что в нашем дизайне мобильные устройства — это устройства с экраном шириной менее 992 пикселей.

Обернём <nav> в див с классом navHolder — он пригодится нам для прокрутки и обрезания меню:

<header class="header">
  <div class="logo">…</div>

  <div class="navHolder">
    <nav class="nav">
      <!-- … -->
    </nav>
  </div>

  <div class="tel">…</div>
</header>

Поменяем раскладку шапки. Вместо трёх колонок сделаем сетку 2×2:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
}

Переставим телефон в правый верхний угол, меню поставим вниз и растянем на две колонки:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
  }
}

Растянем меню на всю ширину экрана:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
    margin-left: -10px;
    margin-right: -10px;
  }
}

Включим горизонтальную прокрутку в меню:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
    margin-left: -10px;
    margin-right: -10px;
  }
  
  .nav {
    overflow-x: scroll; /* Зададим возможность горизонтальной прокрутки */
    -webkit-overflow-scrolling: touch; /* Для Сафари дополнительно зададим инерцию при прокрутке. С инерцией меню будет «оттягиваться» при попытке прокрутить меню за его пределы. */
  }
}

Из‑за overflow обрезались поля у текущего пункта в меню. Плюс при прокрутке появляется горизональный скролбар. Устраним эти проблемы:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
    margin-left: -10px;
    margin-right: -10px;
  }
  
  .nav {
    padding: 10px; /* Добавим дополнительные поля */
    margin: -10px 0; /* С помощью отрицательных отступов сверху-снизу компенсируем «нарощенные» поля */
    scrollbar-width: none; /* Скроем скролбар в Хроме и Файрфоксе */
    overflow-x: scroll;
    -webkit-overflow-scrolling: touch;
  }

  .nav::-webkit-scrollbar {
    display: none; /* Скроем скролбар в Сафари */
  }
}

Добавим немного косметики в виде «забеления» по краям с помощью маски и градиента:

@media (width < 992px) {
  .header {
    grid-template-columns: min-content 1fr;
    grid-template-rows: min-content min-content;
    row-gap: 15px;
  }
  
  .header .tel {
    grid-column: 2;
    grid-row: 1;
  }
  
  .header .navHolder {
    grid-column: 1 / span 2;
    grid-row: 2;
    margin-left: -10px;
    margin-right: -10px;
  }

  .nav {
    padding: 10px;
    margin: -10px 0;
    scrollbar-width: none;
    overflow-x: scroll;
    -webkit-overflow-scrolling: touch;
    mask-image: linear-gradient(
      90deg,
      transparent 0,
      rgba(0, 0, 0, .25) 8px,
      #000 16px,
      #000 calc(100% - 16px),
      rgba(0, 0, 0, .25) calc(100% - 8px),
      transparent
    );
  }

  .nav::-webkit-scrollbar {
    display: none;
  }
}

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

@media (width < 992px) {
  .navHolder {
    position: relative; /* Мы используем обертку для линеек, потому что линейки у .nav попадали бы под маску и забеление */
  }

  .navHolder::before,
  .navHolder::after {
    content: ''; /* Используем псевдоэлементы, а не рамку, потому что их позицией можно управлять */
    position: absolute;
    left: 0;
    right: 0;
    height: 1px;
    background: rgba(0, 0, 0, .07);
  }

  .navHolder::before {
    top: -9px;
  }

  .navHolder::after {
    bottom: -8px;
  }
}

Осталась одна проблема. При некоторых ширинах экрана меню обрезается так, что кажется, что всё поместилось и прокручивать нечего. Чтобы поправить это, можно написать небольшой скрипт, который будет анализировать ширину элементов и менять значение column-gap в меню так, чтобы последний видимый элемент всегда обрезался.

Если меню статичное и вряд ли будет меняться, проще задать подходящие значения column-gap для разной ширины экрана.

Адап­ти­руем шапку к мобиль­ной вер­сии с помо­щью гам­бур­гер­ного меню. Будет скры­вать меню и пока­зы­вать «гам­бур­гер» при той ширине экрана, при кото­рой меню не поме­ща­ется в него целиком.

Возь­мём шапку без мобиль­ной вер­сии и доба­вим кнопку гамбургер:

<header class="header">
  <div class="logo">
    <a href="/">
      <img src="logo.svg" width="40" height="40">
    </a>
  </div>
  
  <nav class="nav">
    <ul>
      <li class="nav__item">
        <a href="/products/">Продукция</a>
      </li>
      <li class="nav__item">
        <a href="/services/">Услуги</a>
      </li>
      <li class="nav__item is__active">
        <a href="/blog/">Блог</a>
      </li>
      <li class="nav__item">
        <a href="/contacts/">Контакты</a>
      </li>
      <li class="nav__item">
        <a href="tel:+74954005050">+7 495 400‑50‑50</a>
      </li>
    </ul>
  </nav>
  
  <div class="tel">
    <a href="tel:+74954005050">
      +7 495 400‑50‑50
    </a>
  </div>

  <!-- aria-label подскажет смысл кнопки программам чтения с экрана -->
  <button class="hamburger" type="button" aria-label="Открыть меню">
    <span class="hamburger-box">
      <span class="hamburger-inner"></span>
    </span>
  </button>
</header>

Поме­няем рас­кладку на мобиль­ных устрой­ствах. Пере­ста­вим теле­фон и кнопку к лого­типу, а меню вниз. Заодно скроем кнопку на доста­точно широ­ких экранах:

@media (width > 690px) {
  .header .hamburger {
    display: none;
  }
}

@media (width <= 690px) {
  .header {
    position: relative; /* Задаём контекст для позиционирования меню */
    grid-template-columns: min-content 1fr 1fr min-content; /* Определяем четыре колонки: первую и последнюю — по ширине логотипа и кнопки, вторую и третью — 50% от оставшегося места */
  }

  .header .hamburger {
    grid-column: 4; /* Кнопку ставим в последнюю колонку */
  }

  .header .tel {
    grid-column: 2 / span 2; /* Телефону отдаём две центральные колонки */
    justify-self: stretch;
  }

  .nav {
    position: absolute; /* Меню позиционируем абсолютно, сразу после шапки поверх содержимого. Из-за этого кнопка перестанет нажиматься, исправим это на следующем шаге */
    top: 0;
    left: 0;
    width: 100%; /* Растягиваем меню на всю ширину экрана */
    padding: 65px 10px 20px; /* Добавляем настолько большой верхний падинг, чтобы меню встало точно под шапкой */
  }

  .nav ul {
    grid-auto-flow: row; /* Раскладываем меню рядами */
    grid-auto-rows: max-content;
    row-gap: 10px;
  }
}

Сти­ли­зуем кнопку. Для про­стоты возь­мём гото­вые стили и ани­ма­цию из биб­лио­теки «гам­бур­гер­ных» ико­нок на ЦСС:

<link rel="stylesheet" src="styles/hamburgers.css">

<header class="header">
  <div class="logo">
    <a href="/">
      <img src="logo.svg" width="40" height="40">
    </a>
  </div>
  
  <nav class="nav">
    <ul>
      <li class="nav__item">
        <a href="/products/">Продукция</a>
      </li>
      <li class="nav__item">
        <a href="/services/">Услуги</a>
      </li>
      <li class="nav__item is__active">
        <a href="/blog/">Блог</a>
      </li>
      <li class="nav__item">
        <a href="/contacts/">Контакты</a>
      </li>
      <li class="nav__item">
        <a href="tel:+74954005050">+7 495 400‑50‑50</a>
      </li>
    </ul>
  </nav>
  
  <div class="tel">
    <a href="tel:+74954005050">
      +7 495 400‑50‑50
    </a>
  </div>

  <button class="hamburger hamburger--squeeze" type="button" aria-label="Открыть меню">
    <span class="hamburger-box">
      <span class="hamburger-inner"></span>
    </span>
  </button>
</header>
.header .hamburger {
  grid-column: 4;
  grid-row: 1;
  color: inherit; /* Чтобы иконка унаследовала цвет текста, а не была белой */
  line-height: 0; /* Чтобы иконка не увеличивала высоту меню */
  padding: 0;
}

.header .hamburger-inner,
.header .hamburger-inner::before,
.header .hamburger-inner::after {
  border-radius: 0; /* Убираем скругления в гамбургере */
}

«Гамбургер» и его анимацию можно было бы сверстать вручную или заменить на иконки

Научим кнопку «кли­каться». Пус­кай пока она меняет своё состо­я­ние и добав­ляет или уби­рает класс у меню:

<script>
  document
    .querySelectorAll('.hamburger') // Находим кнопку-гамбургер
    .forEach(button => {
     const nav = button
      .closest('.header')
      .querySelector('.nav') // Находим ближайшее к кнопке меню

     button.addEventListener('click', () => { // По клику на кнопку добавляем или удаляем класс is-active у кнопки и меню
       button
         .classList
         .toggle('is-active')

       nav
        .classList
        .toggle('is-active')
     })
  })
</script>

Внутри <script> пишут текст на Яваскрипте — языке программирования, встроенном в браузеры. Яваскрипт используют для создания интерактивных страниц и веб‑приложений

Под­клю­чим меню к кнопке и доба­вим ани­ма­цию. Пусть меню при­ез­жает сверху:

 @media (width > 690px) {
   .header .hamburger {
     display: none;
   }
 }

 @media (width <= 690px) {
   .header {
     position: relative;
     grid-template-columns: min-content 1fr 1fr min-content;
   }
   
   .header .hamburger {
     grid-column: 4;
     grid-row: 1;
   }
   
   .header .tel {
     grid-column: 2 / span 2;
     grid-row: 1;
     justify-self: stretch;
   }

   .nav {
     position: absolute;
     top: 0;
     left: 0;
     width: 100%;
     padding: 65px 10px 20px;
     transform: translateY(-100%); /* Прячем меню за верхнюю границу экрана */
     transition: transform 75ms ease; /* Анимируем изменение позиции меню, синхронизировав с анимацией кнопки */
     transition-delay: 120ms;
   }

   .nav.is-active {
     transform: translateY(0); /* Когда меню активно, ставим его под шапку */
     transition-timing-function: cubic-bezier(.215, .61, .355, 1);
   }

   .nav ul {
     grid-auto-flow: row;
     grid-auto-rows: max-content;
     row-gap: 10px;
   }
}

Тайминги и скорость анимации скопировали из кнопки, чтобы не было рассинхрона

Появи­лась про­блема с напол­за­нием. Выез­жа­ю­щее меню пере­кры­вает лого­тип. Попра­вим это, доба­вив слой, раз­де­ля­ю­щий выез­жа­ю­щее меню и лого­тип с кнопкой:

 @media (width > 690px) {
   .header .hamburger {
     display: none;
   }
 }

 @media (width <= 690px) {
   .header {
     position: relative;
     grid-template-columns: min-content 1fr 1fr min-content;
   }
   
   .header .hamburger {
     grid-column: 4;
     grid-row: 1;
   }
   
   .header .tel {
     grid-column: 2 / span 2;
     grid-row: 1;
     justify-self: stretch;
   }

   .nav {
     position: absolute;
     top: 0;
     left: 0;
     width: 100%;
     padding: 65px 10px 20px;
     transform: translateY(calc(60px - 100%)); /* Спрячем меню под слой-разделитель */
     z-index: 1; /* Меню поедет на самом нижнем уровне */
     transition: transform 75ms ease;
     transition-delay: 120ms;
   }
  
  .nav.is-active {
    transform: translateY(0);
    transition-timing-function: cubic-bezier(.215, .61, .355, 1);
  }
  
  .nav ul {
     grid-auto-flow: row;
     grid-auto-rows: max-content;
     row-gap: 10px;
   }

  .header::before {
    content: '';
    background: inherit;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 2; /* Слой-разделитель скроет меню, пока оно проезжает под шапкой */
  }

  .header .logo,
  .header .tel,
  .header .hamburger {
    position: relative;
    z-index: 3; /* Логотип, телефон и кнопка встанут на самом верху */
  }
}

Адаптируем шапку к мобильной версии с помощью гамбургерного меню. Будет скрывать меню и показывать «гамбургер» при той ширине экрана, при которой меню не помещается в него целиком.

Возьмём шапку без мобильной версии и добавим кнопку гамбургер:

<header class="header">
  <div class="logo">
    <a href="/">
      <img src="logo.svg" width="40" height="40">
    </a>
  </div>
  
  <nav class="nav">
    <ul>
      <li class="nav__item">
        <a href="/products/">Продукция</a>
      </li>
      <li class="nav__item">
        <a href="/services/">Услуги</a>
      </li>
      <li class="nav__item is__active">
        <a href="/blog/">Блог</a>
      </li>
      <li class="nav__item">
        <a href="/contacts/">Контакты</a>
      </li>
      <li class="nav__item">
        <a href="tel:+74954005050">+7 495 400‑50‑50</a>
      </li>
    </ul>
  </nav>
  
  <div class="tel">
    <a href="tel:+74954005050">
      +7 495 400‑50‑50
    </a>
  </div>

  <!-- aria-label подскажет смысл кнопки программам чтения с экрана -->
  <button class="hamburger" type="button" aria-label="Открыть меню">
    <span class="hamburger-box">
      <span class="hamburger-inner"></span>
    </span>
  </button>
</header>

Поменяем раскладку на мобильных устройствах. Переставим телефон и кнопку к логотипу, а меню вниз. Заодно скроем кнопку на достаточно широких экранах:

@media (width > 690px) {
  .header .hamburger {
    display: none;
  }
}

@media (width <= 690px) {
  .header {
    position: relative; /* Задаём контекст для позиционирования меню */
    grid-template-columns: min-content 1fr 1fr min-content; /* Определяем четыре колонки: первую и последнюю — по ширине логотипа и кнопки, вторую и третью — 50% от оставшегося места */
  }

  .header .hamburger {
    grid-column: 4; /* Кнопку ставим в последнюю колонку */
  }

  .header .tel {
    grid-column: 2 / span 2; /* Телефону отдаём две центральные колонки */
    justify-self: stretch;
  }

  .nav {
    position: absolute; /* Меню позиционируем абсолютно, сразу после шапки поверх содержимого. Из-за этого кнопка перестанет нажиматься, исправим это на следующем шаге */
    top: 0;
    left: 0;
    width: 100%; /* Растягиваем меню на всю ширину экрана */
    padding: 65px 10px 20px; /* Добавляем настолько большой верхний падинг, чтобы меню встало точно под шапкой */
  }

  .nav ul {
    grid-auto-flow: row; /* Раскладываем меню рядами */
    grid-auto-rows: max-content;
    row-gap: 10px;
  }
}

Стилизуем кнопку. Для простоты возьмём готовые стили и анимацию из библиотеки «гамбургерных» иконок на ЦСС:

<link rel="stylesheet" src="styles/hamburgers.css">

<header class="header">
  <div class="logo">
    <a href="/">
      <img src="logo.svg" width="40" height="40">
    </a>
  </div>
  
  <nav class="nav">
    <ul>
      <li class="nav__item">
        <a href="/products/">Продукция</a>
      </li>
      <li class="nav__item">
        <a href="/services/">Услуги</a>
      </li>
      <li class="nav__item is__active">
        <a href="/blog/">Блог</a>
      </li>
      <li class="nav__item">
        <a href="/contacts/">Контакты</a>
      </li>
      <li class="nav__item">
        <a href="tel:+74954005050">+7 495 400‑50‑50</a>
      </li>
    </ul>
  </nav>
  
  <div class="tel">
    <a href="tel:+74954005050">
      +7 495 400‑50‑50
    </a>
  </div>

  <button class="hamburger hamburger--squeeze" type="button" aria-label="Открыть меню">
    <span class="hamburger-box">
      <span class="hamburger-inner"></span>
    </span>
  </button>
</header>
.header .hamburger {
  grid-column: 4;
  grid-row: 1;
  color: inherit; /* Чтобы иконка унаследовала цвет текста, а не была белой */
  line-height: 0; /* Чтобы иконка не увеличивала высоту меню */
  padding: 0;
}

.header .hamburger-inner,
.header .hamburger-inner::before,
.header .hamburger-inner::after {
  border-radius: 0; /* Убираем скругления в гамбургере */
}

«Гамбургер» и его анимацию можно было бы сверстать вручную или заменить на иконки

Научим кнопку «кликаться». Пускай пока она меняет своё состояние и добавляет или убирает класс у меню:

<script>
  document
    .querySelectorAll('.hamburger') // Находим кнопку-гамбургер
    .forEach(button => {
     const nav = button
      .closest('.header')
      .querySelector('.nav') // Находим ближайшее к кнопке меню

     button.addEventListener('click', () => { // По клику на кнопку добавляем или удаляем класс is-active у кнопки и меню
       button
         .classList
         .toggle('is-active')

       nav
        .classList
        .toggle('is-active')
     })
  })
</script>

Внутри <script> пишут текст на Яваскрипте — языке программирования, встроенном в браузеры. Яваскрипт используют для создания интерактивных страниц и веб‑приложений

Подключим меню к кнопке и добавим анимацию. Пусть меню приезжает сверху:

 @media (width > 690px) {
   .header .hamburger {
     display: none;
   }
 }

 @media (width <= 690px) {
   .header {
     position: relative;
     grid-template-columns: min-content 1fr 1fr min-content;
   }
   
   .header .hamburger {
     grid-column: 4;
     grid-row: 1;
   }
   
   .header .tel {
     grid-column: 2 / span 2;
     grid-row: 1;
     justify-self: stretch;
   }

   .nav {
     position: absolute;
     top: 0;
     left: 0;
     width: 100%;
     padding: 65px 10px 20px;
     transform: translateY(-100%); /* Прячем меню за верхнюю границу экрана */
     transition: transform 75ms ease; /* Анимируем изменение позиции меню, синхронизировав с анимацией кнопки */
     transition-delay: 120ms;
   }

   .nav.is-active {
     transform: translateY(0); /* Когда меню активно, ставим его под шапку */
     transition-timing-function: cubic-bezier(.215, .61, .355, 1);
   }

   .nav ul {
     grid-auto-flow: row;
     grid-auto-rows: max-content;
     row-gap: 10px;
   }
}

Тайминги и скорость анимации скопировали из кнопки, чтобы не было рассинхрона

Появилась проблема с наползанием. Выезжающее меню перекрывает логотип. Поправим это, добавив слой, разделяющий выезжающее меню и логотип с кнопкой:

 @media (width > 690px) {
   .header .hamburger {
     display: none;
   }
 }

 @media (width <= 690px) {
   .header {
     position: relative;
     grid-template-columns: min-content 1fr 1fr min-content;
   }
   
   .header .hamburger {
     grid-column: 4;
     grid-row: 1;
   }
   
   .header .tel {
     grid-column: 2 / span 2;
     grid-row: 1;
     justify-self: stretch;
   }

   .nav {
     position: absolute;
     top: 0;
     left: 0;
     width: 100%;
     padding: 65px 10px 20px;
     transform: translateY(calc(60px - 100%)); /* Спрячем меню под слой-разделитель */
     z-index: 1; /* Меню поедет на самом нижнем уровне */
     transition: transform 75ms ease;
     transition-delay: 120ms;
   }
  
  .nav.is-active {
    transform: translateY(0);
    transition-timing-function: cubic-bezier(.215, .61, .355, 1);
  }
  
  .nav ul {
     grid-auto-flow: row;
     grid-auto-rows: max-content;
     row-gap: 10px;
   }

  .header::before {
    content: '';
    background: inherit;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 2; /* Слой-разделитель скроет меню, пока оно проезжает под шапкой */
  }

  .header .logo,
  .header .tel,
  .header .hamburger {
    position: relative;
    z-index: 3; /* Логотип, телефон и кнопка встанут на самом верху */
  }
}

Свер­стаем про­стой под­вал. Сосре­до­то­чимся только на рас­кладке, опу­стив типо­гра­фику и стили для сброса.

Нач­нём с раз­метки. Собе­рём под­вал в кон­тей­нере <footer>. Для копи­рай­тов и адреса возь­мём дивы. Ссылки на соц­сети собе­рём в нену­ме­ро­ван­ный спи­сок <ul>

<footer class="footer">
  <div class="footer__copyright">
    <p>© Айс-бейби</p>
    <p>
      <a href="mailto:mail@icebaby.ru">
        mail@icebaby.ru
      </a>
      <br>
      +7 495 400‑50‑50
    </p>
  </div>

  <div class="footer__address">
    <p>
      <a href="/welcome/">Стать клиентом</a>
    </p>
    <p>
      Большая улица, 36, строение 2
      <br>
      Москва, Россия, 127015
    </p>
  </div>

  <ul class="footer__social">
    <li>
      <a href="https://t.me/icebaby">Телеграм</a>
    </li>
    <li>
      <a href="https://vk.com/icebaby">VK</a>
    </li>
  </ul>
</footer>

Зада­дим рас­кладку для под­вала целиком:

.footer {
  display: grid;
  grid-template-columns: max-content max-content 1fr; /* Пусть копирайты и адрес займут минимум места, а соцсети — всё оставшееся */
  padding: 10px;
  column-gap: 40px;
}

Зада­дим отступы между абзацами:

.footer {
  display: grid;
  grid-template-columns: max-content max-content 1fr;
  padding: 10px;
  column-gap: 40px;
}

.footer * + p {
  margin-top: 1em;
}

Пре­вра­тим спи­сок ссы­лок на соц­сети в грид‑кон­тей­нер с авто­ма­ти­че­ской рас­клад­кой по колон­кам, чтобы ссылки выстро­и­лись в строку:

.footer__social {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content; /* Автоматически создаём колонки по количеству ссылок с шириной по их содержимому */
  column-gap: 20px; /* Задаём межколонник в 20 пк */
}

В колонке с соц­се­тями оста­лось сво­бод­ное место, эле­менты при­жа­лись к левому краю. Ото­жмём ссылки вниз и вправо, но только на десктопе:

@media (width >= 992px) {
  .footer__social {
    align-content: end;
    justify-self: end;
  }
}

Заме­ним тек­сто­вые ссылки на соц­сети икон­ками в СВГ:

<!-- СВГ-спрайт с иконками -->
<svg width="0" height="0" class="hidden">
  <symbol id="telegram" fill="none" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 48 48">
    <path fill-rule="evenodd" d="M46 24c0 12.15-9.85 22-22 22S2 36.15 2 24 11.85 2 24 2s22 9.85 22 22Zm-29.66-2.22c-2.08.91-4.22 1.84-6.17 2.92-1.02.75.34 1.28 1.61 1.77.2.08.4.16.59.23l.48.15c1.41.45 2.99.95 4.37.2 2.26-1.3 4.39-2.8 6.51-4.29.7-.49 1.39-.98 2.1-1.46l.11-.07c.6-.39 1.94-1.26 1.44-.06-1.18 1.28-2.43 2.42-3.7 3.57-.87.77-1.71 1.56-2.55 2.37-.73.59-1.48 1.78-.67 2.61 1.88 1.31 3.78 2.6 5.69 3.88.62.42 1.24.83 1.86 1.25 1.05.84 2.69.16 2.92-1.15l.31-1.81c.57-3.33 1.14-6.65 1.64-9.99.07-.52.15-1.05.22-1.57.19-1.27.38-2.54.44-3.82-.15-1.27-1.7-.99-2.55-.71-4.42 1.68-8.79 3.49-13.14 5.32-.49.22-.99.44-1.5.66h-.01Z" fill="#000" />
  </symbol>
  <symbol id="vk" fill="none" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 48 48">
    <path fill-rule="evenodd" d="M2 24C2 11.85 11.85 2 24 2s22 9.85 22 22-9.85 22-22 22S2 36.15 2 24Zm23.35 8.21s.49-.05.75-.32c.23-.24.22-.71.22-.71s-.03-2.16.99-2.48c1.01-.31 2.3 2.09 3.68 3.01 1.03.7 1.82.54 1.82.54l3.67-.05s1.92-.12 1.01-1.6c-.07-.12-.53-1.1-2.73-3.1-2.3-2.09-1.99-1.76.78-5.38 1.69-2.21 2.36-3.55 2.15-4.13-.2-.55-1.44-.4-1.44-.4l-4.13.02s-.31-.04-.53.09c-.22.13-.36.44-.36.44s-.65 1.71-1.52 3.16c-1.84 3.06-2.57 3.23-2.88 3.04-.7-.44-.52-1.78-.52-2.73 0-2.97.46-4.21-.89-4.53-.45-.11-.78-.18-1.93-.19-1.47-.01-2.72 0-3.43.34-.47.22-.83.73-.61.76.27.04.89.16 1.22.6.42.56.41 1.83.41 1.83s.24 3.5-.57 3.93c-.56.3-1.32-.31-2.96-3.09-.84-1.42-1.47-3-1.47-3s-.12-.29-.34-.45c-.26-.19-.63-.25-.63-.25l-3.92.03s-.59.02-.81.27c-.19.22-.02.68-.02.68s3.07 7.05 6.55 10.61c3.19 3.26 6.81 3.05 6.81 3.05h1.64l-.01.01Z" fill="#000" />
  </symbol>
</svg>

<footer class="footer">
  <div class="footer__copyright">
    <p>© Айс-бейби</p>
    <p>
      <a href="mailto:mail@icebaby.ru">
        mail@icebaby.ru
      </a>
      <br>
      +7 495 400‑50‑50
    </p>
  </div>
  
  <div class="footer__address">
    <p>
      <a href="/welcome/">Стать клиентом</a>
    </p>
    <p>
      Большая улица, 36, строение 2
      <br>
      Москва, Россия, 127015
    </p>
  </div>

  <ul class="footer__social">
    <li>
      <a href="https://t.me/icebaby">
        <svg class="icon" width="36" height="36">
          <use xlink:href="#telegram"></use>
        </svg>
      </a>
    </li>
    <li>
      <a href="https://vk.com/icebaby">
        <svg class="icon" width="36" height="36">
          <use xlink:href="#vk"></use>
        </svg>
      </a>
    </li>
  </ul>
</footer>

Иконки — social‑icons на Гитхабе, спрайты — SVGSprit.es

Доба­вим мобиль­ную вер­сию, выстроив всё в одну колонку:

@media (width < 992px) {
  .footer {
    grid-template-columns: 1fr; /* Меняем сетку на 1 колонку и N рядов с отступом в 1.25em */
    grid-auto-rows: min-content;
    row-gap: 1.25em;
  }
}

Типичный подвал из трёх колонок: копирайтов, адреса и ссылок на соцсети

Иногда в подвалах размещают кладбище ссылок, дублируя меню из шапки сайта

Используем max‑content, а не min‑content, чтобы адрес из нескольких слов не переносился

По умолчанию браузеры задают отступы в 1em до и после абзацев

Было бы здорово вынести значения межколонников в ЦСС‑переменные

На мобильных ссылки на соцсети останутся прижатыми к левому краю

СВГ‑спрайт — одна картинка со всеми иконками, которые можно использовать отдельно

Стать клиентом

Большая улица, 36, строение 2
Москва, Россия, 127015

Меняем сетку, раскладывая всё в одну колонку и N рядов по содержимому

Стать клиентом

Большая улица, 36, строение 2
Москва, Россия, 127015

Стать клиентом

Большая улица, 36, строение 2
Москва, Россия, 127015

Типичный подвал из трёх колонок: копирайтов, адреса и ссылок на соцсети

Иногда в подвалах размещают кладбище ссылок, дублируя меню из шапки сайта

Используем max‑content, а не min‑content, чтобы адрес из нескольких слов не переносился

По умолчанию браузеры задают отступы в 1em до и после абзацев

Было бы здорово вынести значения межколонников в ЦСС‑переменные

На мобильных ссылки на соцсети останутся прижатыми к левому краю

СВГ‑спрайт — одна картинка со всеми иконками, которые можно использовать отдельно

Сверстаем простой подвал. Сосредоточимся только на раскладке, опустив типографику и стили для сброса.

Начнём с разметки. Соберём подвал в контейнере <footer>. Для копирайтов и адреса возьмём дивы. Ссылки на соцсети соберём в ненумерованный список <ul>

<footer class="footer">
  <div class="footer__copyright">
    <p>© Айс-бейби</p>
    <p>
      <a href="mailto:mail@icebaby.ru">
        mail@icebaby.ru
      </a>
      <br>
      +7 495 400‑50‑50
    </p>
  </div>

  <div class="footer__address">
    <p>
      <a href="/welcome/">Стать клиентом</a>
    </p>
    <p>
      Большая улица, 36, строение 2
      <br>
      Москва, Россия, 127015
    </p>
  </div>

  <ul class="footer__social">
    <li>
      <a href="https://t.me/icebaby">Телеграм</a>
    </li>
    <li>
      <a href="https://vk.com/icebaby">VK</a>
    </li>
  </ul>
</footer>

Зададим раскладку для подвала целиком:

.footer {
  display: grid;
  grid-template-columns: max-content max-content 1fr; /* Пусть копирайты и адрес займут минимум места, а соцсети — всё оставшееся */
  padding: 10px;
  column-gap: 40px;
}

Зададим отступы между абзацами:

.footer {
  display: grid;
  grid-template-columns: max-content max-content 1fr;
  padding: 10px;
  column-gap: 40px;
}

.footer * + p {
  margin-top: 1em;
}

Превратим список ссылок на соцсети в грид‑контейнер с автоматической раскладкой по колонкам, чтобы ссылки выстроились в строку:

.footer__social {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content; /* Автоматически создаём колонки по количеству ссылок с шириной по их содержимому */
  column-gap: 20px; /* Задаём межколонник в 20 пк */
}

В колонке с соцсетями осталось свободное место, элементы прижались к левому краю. Отожмём ссылки вниз и вправо, но только на десктопе:

@media (width >= 992px) {
  .footer__social {
    align-content: end;
    justify-self: end;
  }
}

Заменим текстовые ссылки на соцсети иконками в СВГ:

<!-- СВГ-спрайт с иконками -->
<svg width="0" height="0" class="hidden">
  <symbol id="telegram" fill="none" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 48 48">
    <path fill-rule="evenodd" d="M46 24c0 12.15-9.85 22-22 22S2 36.15 2 24 11.85 2 24 2s22 9.85 22 22Zm-29.66-2.22c-2.08.91-4.22 1.84-6.17 2.92-1.02.75.34 1.28 1.61 1.77.2.08.4.16.59.23l.48.15c1.41.45 2.99.95 4.37.2 2.26-1.3 4.39-2.8 6.51-4.29.7-.49 1.39-.98 2.1-1.46l.11-.07c.6-.39 1.94-1.26 1.44-.06-1.18 1.28-2.43 2.42-3.7 3.57-.87.77-1.71 1.56-2.55 2.37-.73.59-1.48 1.78-.67 2.61 1.88 1.31 3.78 2.6 5.69 3.88.62.42 1.24.83 1.86 1.25 1.05.84 2.69.16 2.92-1.15l.31-1.81c.57-3.33 1.14-6.65 1.64-9.99.07-.52.15-1.05.22-1.57.19-1.27.38-2.54.44-3.82-.15-1.27-1.7-.99-2.55-.71-4.42 1.68-8.79 3.49-13.14 5.32-.49.22-.99.44-1.5.66h-.01Z" fill="#000" />
  </symbol>
  <symbol id="vk" fill="none" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 48 48">
    <path fill-rule="evenodd" d="M2 24C2 11.85 11.85 2 24 2s22 9.85 22 22-9.85 22-22 22S2 36.15 2 24Zm23.35 8.21s.49-.05.75-.32c.23-.24.22-.71.22-.71s-.03-2.16.99-2.48c1.01-.31 2.3 2.09 3.68 3.01 1.03.7 1.82.54 1.82.54l3.67-.05s1.92-.12 1.01-1.6c-.07-.12-.53-1.1-2.73-3.1-2.3-2.09-1.99-1.76.78-5.38 1.69-2.21 2.36-3.55 2.15-4.13-.2-.55-1.44-.4-1.44-.4l-4.13.02s-.31-.04-.53.09c-.22.13-.36.44-.36.44s-.65 1.71-1.52 3.16c-1.84 3.06-2.57 3.23-2.88 3.04-.7-.44-.52-1.78-.52-2.73 0-2.97.46-4.21-.89-4.53-.45-.11-.78-.18-1.93-.19-1.47-.01-2.72 0-3.43.34-.47.22-.83.73-.61.76.27.04.89.16 1.22.6.42.56.41 1.83.41 1.83s.24 3.5-.57 3.93c-.56.3-1.32-.31-2.96-3.09-.84-1.42-1.47-3-1.47-3s-.12-.29-.34-.45c-.26-.19-.63-.25-.63-.25l-3.92.03s-.59.02-.81.27c-.19.22-.02.68-.02.68s3.07 7.05 6.55 10.61c3.19 3.26 6.81 3.05 6.81 3.05h1.64l-.01.01Z" fill="#000" />
  </symbol>
</svg>

<footer class="footer">
  <div class="footer__copyright">
    <p>© Айс-бейби</p>
    <p>
      <a href="mailto:mail@icebaby.ru">
        mail@icebaby.ru
      </a>
      <br>
      +7 495 400‑50‑50
    </p>
  </div>
  
  <div class="footer__address">
    <p>
      <a href="/welcome/">Стать клиентом</a>
    </p>
    <p>
      Большая улица, 36, строение 2
      <br>
      Москва, Россия, 127015
    </p>
  </div>

  <ul class="footer__social">
    <li>
      <a href="https://t.me/icebaby">
        <svg class="icon" width="36" height="36">
          <use xlink:href="#telegram"></use>
        </svg>
      </a>
    </li>
    <li>
      <a href="https://vk.com/icebaby">
        <svg class="icon" width="36" height="36">
          <use xlink:href="#vk"></use>
        </svg>
      </a>
    </li>
  </ul>
</footer>

Иконки — social‑icons на Гитхабе, спрайты — SVGSprit.es

Добавим мобильную версию, выстроив всё в одну колонку:

@media (width < 992px) {
  .footer {
    grid-template-columns: 1fr; /* Меняем сетку на 1 колонку и N рядов с отступом в 1.25em */
    grid-auto-rows: min-content;
    row-gap: 1.25em;
  }
}

Стать клиентом

Большая улица, 36, строение 2
Москва, Россия, 127015

Визитка

Визитка — это стра­ница с тек­стом о себе. Часто визитку сов­ме­щают с резюме, добав­ляя опыт работы, навыки, доклады и выступ­ле­ния на кон­фе­рен­циях. Боль­шин­ство визи­ток состоит из тек­ста, списка ссы­лок и фото­гра­фии автора, зада­ю­щей настро­е­ние. Они не очень объ­ём­ные, но с типо­гра­фи­че­скими изысками.

Визитка

Визитка — это страница с текстом о себе. Часто визитку совмещают с резюме, добавляя опыт работы, навыки, доклады и выступления на конференциях. Большинство визиток состоит из текста, списка ссылок и фотографии автора, задающей настроение. Они не очень объёмные, но с типографическими изысками.

Привет! Я — Растин Спенсер Коул, дизайнер, музыкант и гроссмейстер. Проектирую сайты, приложения и системы навигации в общественных местах. Пишу музыку, играю в шахматы.

Свер­стаем про­стую стра­ницу‑визитку с рас­ска­зом о себе. Вме­сто фото­гра­фии возь­мём кру­жок‑заглушку. Нач­нём с разметки:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Чтобы сайт отображался в одинаковом масштабе на мобильных -->
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
  <title>Растин Спенсер Коул</title>
</head>
<body>
  <div class="layoutContainer">
    <header class="header">
      <a href="/" class="logo">
        <svg viewBox="0 0 64 64" width="64" height="64">
          <circle cx="32" cy="32" r="30"></circle>
        </svg>
      </a>
      <nav class="nav">
        <a href="/design/">Дизайн</a>
        <a href="/music/">Музыка</a>
        <a href="/chess/">Шахматы</a>
      </nav>
    </header>

    <section class="about">
      <div class="lead">
        <p>
          Привет! Я — Растин Спенсер Коул,
          дизайнер, музыкант и гроссмейстер. Проектирую <a href="/websites/">сайты</a>,
          <a href="/apps/">приложения</a> и <a href="/wayfinding/">системы навигации</a>
          в общественных местах.
          Пишу <a href="https://www.last.fm/music/Mick+Gordon">музыку</a>,
          играю <a href="https://chessarena.com/profile/218782">в шахматы</a>.
        </p>
      </div>
    </section>
  </div>
</body>
</html>

Под­клю­чим ИБМ Плекс Санс и настроим базо­вую типографику:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@100;400;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
  <title>Растин Спенсер Коул</title>
</head>
<body>
  <div class="layoutContainer">
    <header class="header">
      <a href="/" class="logo">
        <svg viewBox="0 0 64 64" width="64" height="64">
          <circle cx="32" cy="32" r="30"></circle>
        </svg>
      </a>
      <nav class="nav">
        <a href="/design/">Дизайн</a>
        <a href="/music/">Музыка</a>
        <a href="/chess/">Шахматы</a>
      </nav>
    </header>

    <section class="about">
      <div class="lead">
        <p>
          Привет! Я — Растин Спенсер Коул,
          дизайнер, музыкант и гроссмейстер. Проектирую <a href="/websites/">сайты</a>,
          <a href="/apps/">приложения</a> и <a href="/wayfinding/">системы навигации</a>
          в общественных местах.
          Пишу <a href="https://www.last.fm/music/Mick+Gordon">музыку</a>,
          играю <a href="https://chessarena.com/profile/218782">в шахматы</a>.
        </p>
      </div>
    </section>
  </div>
</body>
</html>
:root {
  --textColor: #282828; /* Введём ЦСС-переменные для цвета текста и фона. Они пригодятся нам, когда решим перекрашивать страницу в особых случаях */
  --backgroundColor: rgba(247, 247, 247,.8);
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

Доба­вим рези­но­вые отступы стра­нице, чтобы текст не упи­рался в края окна:

:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem; /* Функция clamp() возвращает значение 2,5% ширины окна, зажатое между минимальным значение в 1,43rem и максимальным в 2,84rem. Где 1rem — кегль страницы на самом высоком уровне, по умолчанию равный 16 пк */
}

Сти­ли­зуем ссылки:

:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
  --keyColor: #00b2ff; /* Ключевой цвет страницы */
  --linkColor: var(--textColor); /* Красим ссылки в цвет текста */
  --linkUnderlineColor: color-mix(in srgb, var(--keyColor) 50%, rgba(255, 255, 255, 0)); /* Подчёркивание делаем светлее ключевого цвета, смешивая его с прозрачным белым */
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

a {
  color: var(--linkColor);
  text-decoration: underline;
  text-decoration-color: var(--linkUnderlineColor);
  text-decoration-thickness: .0625em; /* Пусть толщина зависит от кегля. Чем больше кегль, тем толще подчеркивание */
  text-underline-offset: 0.25em; /* Чуть сдвинем подчеркивание вниз */
}

a:hover {
  color: var(--keyColor);
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem;
}

Раз­ло­жим шапку:

:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
  --keyColor: #00b2ff;
  --linkColor: var(--textColor);
  --linkUnderlineColor: color-mix(in srgb, var(--keyColor) 50%, rgba(255, 255, 255, 0));
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

a {
  color: var(--linkColor);
  text-decoration: underline;
  text-decoration-color: var(--linkUnderlineColor);
  text-decoration-thickness: .0625em;
  text-underline-offset: 0.25em;
}

a:hover {
  color: var(--keyColor);
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem;
}

.header {
  font-size: 1.5rem; /* Увеличиваем кегль в 1,5 раза */
  display: grid; /* Включаем гриды */
  grid-template-columns: min-content 1fr; /* Отдаём логотипу минимум места, всё остальное — меню */
  align-items: center; /* Выравниваем всё по центру */
}

.logo {
  width: 1.75em;
  height: 1.75em;
  margin-right: 1em;
  fill: var(--keyColor);
}

.nav {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end; /* Прижимаем пункты меню к правому краю */
  column-gap: 1rem; /* Задаём отступ между пунктами меню */
}

Сти­ли­зуем лид:

:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
  --keyColor: #00b2ff;
  --linkColor: var(--textColor);
  --linkUnderlineColor: color-mix(in srgb, var(--keyColor) 50%, rgba(255, 255, 255, 0));
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

a {
  color: var(--linkColor);
  text-decoration: underline;
  text-decoration-color: var(--linkUnderlineColor);
  text-decoration-thickness: .0625em;
  text-underline-offset: 0.25em;
}

a:hover {
  color: var(--keyColor);
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem;
}

.header {
  font-size: 1.5rem;
  display: grid;
  grid-template-columns: min-content 1fr;
  align-items: center;
}

.logo {
  width: 1.75em;
  height: 1.75em;
  margin-right: 1em;
  fill: var(--keyColor);
}

.nav {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end;
  column-gap: 1rem;
}


.lead {
  font-size: clamp(2rem, 5vw, 5rem); /* Настраиваем кегль, равный 5% от ширины экрана, но в пределах 32—80 пк */
}

.lead p {
  max-width: 18em; /* Ограничиваем абзацы в лиде по ширине, чтобы текст было удобно читать на широких экранах */
}

.about {
  margin-top: 5vh; /* Добавляем отступ от шапки. Мы специально добавляем его здесь, а не в лиде, чтобы разделить ответственность. Наверняка нам захочется использовать лид на других страницах, в таком случае верхний маржин будет только мешать */
}

Привет! Я — Растин Спенсер Коул, дизайнер, музыкант и гроссмейстер. Проектирую сайты, приложения и системы навигации в общественных местах. Пишу музыку, играю в шахматы.

Сверстаем простую страницу‑визитку с рассказом о себе. Вместо фотографии возьмём кружок‑заглушку. Начнём с разметки:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Чтобы сайт отображался в одинаковом масштабе на мобильных -->
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
  <title>Растин Спенсер Коул</title>
</head>
<body>
  <div class="layoutContainer">
    <header class="header">
      <a href="/" class="logo">
        <svg viewBox="0 0 64 64" width="64" height="64">
          <circle cx="32" cy="32" r="30"></circle>
        </svg>
      </a>
      <nav class="nav">
        <a href="/design/">Дизайн</a>
        <a href="/music/">Музыка</a>
        <a href="/chess/">Шахматы</a>
      </nav>
    </header>

    <section class="about">
      <div class="lead">
        <p>
          Привет! Я — Растин Спенсер Коул,
          дизайнер, музыкант и гроссмейстер. Проектирую <a href="/websites/">сайты</a>,
          <a href="/apps/">приложения</a> и <a href="/wayfinding/">системы навигации</a>
          в общественных местах.
          Пишу <a href="https://www.last.fm/music/Mick+Gordon">музыку</a>,
          играю <a href="https://chessarena.com/profile/218782">в шахматы</a>.
        </p>
      </div>
    </section>
  </div>
</body>
</html>

Подключим ИБМ Плекс Санс и настроим базовую типографику:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@100;400;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
  <title>Растин Спенсер Коул</title>
</head>
<body>
  <div class="layoutContainer">
    <header class="header">
      <a href="/" class="logo">
        <svg viewBox="0 0 64 64" width="64" height="64">
          <circle cx="32" cy="32" r="30"></circle>
        </svg>
      </a>
      <nav class="nav">
        <a href="/design/">Дизайн</a>
        <a href="/music/">Музыка</a>
        <a href="/chess/">Шахматы</a>
      </nav>
    </header>

    <section class="about">
      <div class="lead">
        <p>
          Привет! Я — Растин Спенсер Коул,
          дизайнер, музыкант и гроссмейстер. Проектирую <a href="/websites/">сайты</a>,
          <a href="/apps/">приложения</a> и <a href="/wayfinding/">системы навигации</a>
          в общественных местах.
          Пишу <a href="https://www.last.fm/music/Mick+Gordon">музыку</a>,
          играю <a href="https://chessarena.com/profile/218782">в шахматы</a>.
        </p>
      </div>
    </section>
  </div>
</body>
</html>
:root {
  --textColor: #282828; /* Введём ЦСС-переменные для цвета текста и фона. Они пригодятся нам, когда решим перекрашивать страницу в особых случаях */
  --backgroundColor: rgba(247, 247, 247,.8);
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

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

:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem; /* Функция clamp() возвращает значение 2,5% ширины окна, зажатое между минимальным значение в 1,43rem и максимальным в 2,84rem. Где 1rem — кегль страницы на самом высоком уровне, по умолчанию равный 16 пк */
}

Стилизуем ссылки:

:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
  --keyColor: #00b2ff; /* Ключевой цвет страницы */
  --linkColor: var(--textColor); /* Красим ссылки в цвет текста */
  --linkUnderlineColor: color-mix(in srgb, var(--keyColor) 50%, rgba(255, 255, 255, 0)); /* Подчёркивание делаем светлее ключевого цвета, смешивая его с прозрачным белым */
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

a {
  color: var(--linkColor);
  text-decoration: underline;
  text-decoration-color: var(--linkUnderlineColor);
  text-decoration-thickness: .0625em; /* Пусть толщина зависит от кегля. Чем больше кегль, тем толще подчеркивание */
  text-underline-offset: 0.25em; /* Чуть сдвинем подчеркивание вниз */
}

a:hover {
  color: var(--keyColor);
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem;
}

Разложим шапку:

:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
  --keyColor: #00b2ff;
  --linkColor: var(--textColor);
  --linkUnderlineColor: color-mix(in srgb, var(--keyColor) 50%, rgba(255, 255, 255, 0));
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

a {
  color: var(--linkColor);
  text-decoration: underline;
  text-decoration-color: var(--linkUnderlineColor);
  text-decoration-thickness: .0625em;
  text-underline-offset: 0.25em;
}

a:hover {
  color: var(--keyColor);
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem;
}

.header {
  font-size: 1.5rem; /* Увеличиваем кегль в 1,5 раза */
  display: grid; /* Включаем гриды */
  grid-template-columns: min-content 1fr; /* Отдаём логотипу минимум места, всё остальное — меню */
  align-items: center; /* Выравниваем всё по центру */
}

.logo {
  width: 1.75em;
  height: 1.75em;
  margin-right: 1em;
  fill: var(--keyColor);
}

.nav {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end; /* Прижимаем пункты меню к правому краю */
  column-gap: 1rem; /* Задаём отступ между пунктами меню */
}

Стилизуем лид:

:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
  --keyColor: #00b2ff;
  --linkColor: var(--textColor);
  --linkUnderlineColor: color-mix(in srgb, var(--keyColor) 50%, rgba(255, 255, 255, 0));
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
  min-height: 100vh;
}

a {
  color: var(--linkColor);
  text-decoration: underline;
  text-decoration-color: var(--linkUnderlineColor);
  text-decoration-thickness: .0625em;
  text-underline-offset: 0.25em;
}

a:hover {
  color: var(--keyColor);
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem;
}

.header {
  font-size: 1.5rem;
  display: grid;
  grid-template-columns: min-content 1fr;
  align-items: center;
}

.logo {
  width: 1.75em;
  height: 1.75em;
  margin-right: 1em;
  fill: var(--keyColor);
}

.nav {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end;
  column-gap: 1rem;
}


.lead {
  font-size: clamp(2rem, 5vw, 5rem); /* Настраиваем кегль, равный 5% от ширины экрана, но в пределах 32—80 пк */
}

.lead p {
  max-width: 18em; /* Ограничиваем абзацы в лиде по ширине, чтобы текст было удобно читать на широких экранах */
}

.about {
  margin-top: 5vh; /* Добавляем отступ от шапки. Мы специально добавляем его здесь, а не в лиде, чтобы разделить ответственность. Наверняка нам захочется использовать лид на других страницах, в таком случае верхний маржин будет только мешать */
}

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

В слу­чае с нашей стра­ни­цей под­дер­жим два типа устройств:

Мобильные< 768 пк
Лаптопы‑десктопы≥ 768 пк

На мобиль­ных умень­шим базо­вый раз­мер шрифта до 14 пикселей:

@media (width < 768px) {
  :root {
    font-size: 14px;
  }
}

Умень­шим кегль в шапке:

@media (width < 768px) {
  :root {
    font-size: 14px;
  }
  
  .header {
    font-size: 1.25rem;
  }
}

Уве­ли­чим рас­сто­я­ние между пунк­тами меню, чтобы облег­чить прицеливание:

@media (width < 768px) {
  :root {
    font-size: 14px;
  }
  
  .header {
    font-size: 1.25rem;
  }
  
  .nav {
    column-gap: 1.5em;
  }
}

Привет! Я — Растин Спенсер Коул, дизайнер, музыкант и гроссмейстер. Проектирую сайты, приложения и системы навигации в общественных местах. Пишу музыку, играю в шахматы.

Привет! Я — Растин Спенсер Коул, дизайнер, музыкант и гроссмейстер. Проектирую сайты, приложения и системы навигации в общественных местах. Пишу музыку, играю в шахматы.

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

В случае с нашей страницей поддержим два типа устройств:

Мобильные< 768 пк
Лаптопы‑десктопы≥ 768 пк

На мобильных уменьшим базовый размер шрифта до 14 пикселей:

@media (width < 768px) {
  :root {
    font-size: 14px;
  }
}

Уменьшим кегль в шапке:

@media (width < 768px) {
  :root {
    font-size: 14px;
  }
  
  .header {
    font-size: 1.25rem;
  }
}

Увеличим расстояние между пунктами меню, чтобы облегчить прицеливание:

@media (width < 768px) {
  :root {
    font-size: 14px;
  }
  
  .header {
    font-size: 1.25rem;
  }
  
  .nav {
    column-gap: 1.5em;
  }
}

Привет! Я — Растин Спенсер Коул, дизайнер, музыкант и гроссмейстер. Проектирую сайты, приложения и системы навигации в общественных местах. Пишу музыку, играю в шахматы.

Доба­вим тём­ную тему, если кли­ент её предпочитает:

@media (prefers-color-scheme: dark) {
  :root {
    --textColor: #fff;
    --backgroundColor: #0f0f0f;
  }
}

Привет! Я — Растин Спенсер Коул, дизайнер, музыкант и гроссмейстер. Проектирую сайты, приложения и системы навигации в общественных местах. Пишу музыку, играю в шахматы.

Добавим тёмную тему, если клиент её предпочитает:

@media (prefers-color-scheme: dark) {
  :root {
    --textColor: #fff;
    --backgroundColor: #0f0f0f;
  }
}

Вот, что в итоге, у нас получилось:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@100;400;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
  <title>Растин Спенсер Коул</title>
</head>
<body>
  <div class="layoutContainer">
    <header class="header">
      <a href="/" class="logo">
        <svg viewBox="0 0 64 64" width="64" height="64">
          <circle cx="32" cy="32" r="30"></circle>
        </svg>
      </a>
      <nav class="nav">
        <a href="/design/">Дизайн</a>
        <a href="/music/">Музыка</a>
        <a href="/chess/">Шахматы</a>
      </nav>
    </header>

    <section class="about">
      <div class="lead">
        <p>
          Привет! Я — Растин Спенсер Коул,
          дизайнер, музыкант и гроссмейстер. Проектирую <a href="/websites/">сайты</a>,
          <a href="/apps/">приложения</a> и <a href="/wayfinding/">системы навигации</a>
          в общественных местах.
          Пишу <a href="https://www.last.fm/music/Mick+Gordon">музыку</a>,
          играю <a href="https://chessarena.com/profile/218782">в шахматы</a>.
        </p>
      </div>
    </section>
  </div>
</body>
</html>
:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
  --keyColor: #00b2ff;
  --linkColor: var(--textColor);
  --linkUnderlineColor: color-mix(in srgb, var(--keyColor) 50%, rgba(255, 255, 255, 0));
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
}

a {
  color: var(--linkColor);
  text-decoration: underline;
  text-decoration-color: var(--linkUnderlineColor);
  text-decoration-thickness: .0625em;
  text-underline-offset: 0.25em;
}

a:hover {
  color: var(--keyColor);
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem;
}

.header {
  font-size: 1.5rem;
  display: grid;
  grid-template-columns: min-content 1fr;
  align-items: center;
}

.header a {
  text-decoration: none;
}

.logo {
  width: 1.75em;
  height: 1.75em;
  margin-right: 1em;
  fill: var(--keyColor);
}

.nav {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end;
  column-gap: 1em;
}

.lead {
  font-size: clamp(2rem, 5vw, 5rem);
}

.lead p {
  max-width: 18em;
}

.about {
  margin-top: 5vh;
}

@media (width < 768px) {
  :root {
    font-size: 14px;
  }

  .header {
    font-size: 1.25rem;
  }

  .nav {
    column-gap: 1.5em;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --textColor: #fff;
    --backgroundColor: #0f0f0f;
  }
}

Порастягивайте окно и посмотрите, как меняется вёрстка и кегль лида

Привет! Я — Растин Спенсер Коул, дизайнер, музыкант и гроссмейстер. Проектирую сайты, приложения и системы навигации в общественных местах. Пишу музыку, играю в шахматы.

Привет! Я — Растин Спенсер Коул, дизайнер, музыкант и гроссмейстер. Проектирую сайты, приложения и системы навигации в общественных местах. Пишу музыку, играю в шахматы.

Вот, что в итоге, у нас получилось:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@100;400;700&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
  <title>Растин Спенсер Коул</title>
</head>
<body>
  <div class="layoutContainer">
    <header class="header">
      <a href="/" class="logo">
        <svg viewBox="0 0 64 64" width="64" height="64">
          <circle cx="32" cy="32" r="30"></circle>
        </svg>
      </a>
      <nav class="nav">
        <a href="/design/">Дизайн</a>
        <a href="/music/">Музыка</a>
        <a href="/chess/">Шахматы</a>
      </nav>
    </header>

    <section class="about">
      <div class="lead">
        <p>
          Привет! Я — Растин Спенсер Коул,
          дизайнер, музыкант и гроссмейстер. Проектирую <a href="/websites/">сайты</a>,
          <a href="/apps/">приложения</a> и <a href="/wayfinding/">системы навигации</a>
          в общественных местах.
          Пишу <a href="https://www.last.fm/music/Mick+Gordon">музыку</a>,
          играю <a href="https://chessarena.com/profile/218782">в шахматы</a>.
        </p>
      </div>
    </section>
  </div>
</body>
</html>
:root {
  --textColor: #282828;
  --backgroundColor: rgba(247, 247, 247,.8);
  --keyColor: #00b2ff;
  --linkColor: var(--textColor);
  --linkUnderlineColor: color-mix(in srgb, var(--keyColor) 50%, rgba(255, 255, 255, 0));
}

body {
  font: 1em/1.25 IBM Plex Sans, Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
  background-color: var(--backgroundColor);
}

a {
  color: var(--linkColor);
  text-decoration: underline;
  text-decoration-color: var(--linkUnderlineColor);
  text-decoration-thickness: .0625em;
  text-underline-offset: 0.25em;
}

a:hover {
  color: var(--keyColor);
}

.layoutContainer {
  padding: .5rem clamp(1.43rem, 2.5vw, 2.84rem) 3rem;
}

.header {
  font-size: 1.5rem;
  display: grid;
  grid-template-columns: min-content 1fr;
  align-items: center;
}

.header a {
  text-decoration: none;
}

.logo {
  width: 1.75em;
  height: 1.75em;
  margin-right: 1em;
  fill: var(--keyColor);
}

.nav {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end;
  column-gap: 1em;
}

.lead {
  font-size: clamp(2rem, 5vw, 5rem);
}

.lead p {
  max-width: 18em;
}

.about {
  margin-top: 5vh;
}

@media (width < 768px) {
  :root {
    font-size: 14px;
  }

  .header {
    font-size: 1.25rem;
  }

  .nav {
    column-gap: 1.5em;
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --textColor: #fff;
    --backgroundColor: #0f0f0f;
  }
}

Роман Жалялов, дизайнер интерфейсов

Учусь на второй ступени Школы дизайнеров Бюро Горбунова.

Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».

Свер­стаем стра­ницу‑визитку из работы Романа Жаля­лова в Школе Бюро Горбунова.

Нач­нём с разметки:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Роман Жалялов, дизайнер интерфейсов</title>
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
</head><!-- Стили для сброса, метатеги и заголовок окна страницы -->
<body>
  <div class="layoutContainer">
    <section class="about">
      <img class="photo" src="images/me.jpg"><!-- Будем хранить картинки в папке images -->

      <h1>Роман Жалялов, дизайнер интерфейсов</h1>

      <div class="text">
        <p>Учусь на второй ступени Школы дизайнеров Бюро Горбунова.</p>

        <p>Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».<!-- «травлю леску» склеили неразрывным пробелом --></p>
      </div>

      <footer class="social">
        <a href="">Telegram</a>
        <a href="">Behance</a>
      </footer>
    </section>
  </div>
</body>
</html>

Настроим рас­кладку стра­ницы. Рас­тя­нем основ­ной кон­тей­нер на всю высоту окна и доба­вим поля. Заодно зада­дим фон и цвет текста:

body {
  background: #000;
  color: #fff;
  display: flex;
  min-height: 100vh;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
}

Разо­бьём .about на 4 ячейки и рас­по­ло­жим в них заго­ло­вок, фото­гра­фию, текст и социокнопки:

body {
  background: #000;
  color: #fff;
  display: flex;
  min-height: 100vh;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr; /* Две колонки, делящие свободное пространство в пропорции 77,5:22,5 */
  grid-template-rows: min-content min-content; /* Два ряда высотой по содержимому */
  gap: 20px 10px;
}

.about h1 {
  grid-area: 2 / 1; /* Сокращённая форма записи grid-row: 1; grid-column: 2 */
}

.about .photo {
  grid-area: 1 / 2; /* grid-row: 2; grid-column: 1 */
}

.about .text {
  grid-area: 1 / 1;
}

.about .social {
  grid-area: 2 / 2;
}

Напо­ли­руем рас­кладку. Ото­жмём строки к краям окна и поста­вим соци­окнопки на место:

body {
  background: #000;
  color: #fff;
  display: flex;
  min-height: 100vh;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between; /* Отожмём текст и фотографию к верхнему краю окна, а заголовок и социокнопки — к нижнему */
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end; /* Социокнопки отожмём в правый нижний угол */
  justify-self: end;
}

Настроим типо­гра­фику. Зада­дим основ­ной шрифт, сти­ли­зуем заго­ло­вок и текст:

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw; /* Пусть кегль заголовка зависит от ширины окна */
  line-height: .775; /* Уменьшаем интерлиньяж, чтобы попасть в макет */
  letter-spacing: -.05em; /* Уменьшаем расстояния между буквами */
}

.text {
  font-size: max(18px, 1.9vw); /* Пусть кегль текста также зависит от ширины окна, но с минимальным значением в 18 пикселей */
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}

Разо­бьём текст на 2 колонки:

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: 1fr 1fr; /* Две колонки, делящие свободное пространство в пропорции 1:1 */
  column-gap: 10px;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}

Огра­ни­чим текст по ширине и при­жмём его к фото­гра­фии, чтобы полу­чить узкие колонки, как в макете:

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
  max-width: max(280px, 50%);
  justify-self: end;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}

Вёрстку соци­окно­пок оста­вим чита­телю. Она почти не будет отли­чаться от вёрстки таких же кно­пок в подвале.

Роман Жалялов, дизайнер интерфейсов

Учусь на второй ступени Школы дизайнеров Бюро Горбунова.

Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».

Сверстаем страницу‑визитку из работы Романа Жалялова в Школе Бюро Горбунова.

Начнём с разметки:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Роман Жалялов, дизайнер интерфейсов</title>
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
</head><!-- Стили для сброса, метатеги и заголовок окна страницы -->
<body>
  <div class="layoutContainer">
    <section class="about">
      <img class="photo" src="images/me.jpg"><!-- Будем хранить картинки в папке images -->

      <h1>Роман Жалялов, дизайнер интерфейсов</h1>

      <div class="text">
        <p>Учусь на второй ступени Школы дизайнеров Бюро Горбунова.</p>

        <p>Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».<!-- «травлю леску» склеили неразрывным пробелом --></p>
      </div>

      <footer class="social">
        <a href="">Telegram</a>
        <a href="">Behance</a>
      </footer>
    </section>
  </div>
</body>
</html>

Настроим раскладку страницы. Растянем основной контейнер на всю высоту окна и добавим поля. Заодно зададим фон и цвет текста:

body {
  background: #000;
  color: #fff;
  display: flex;
  min-height: 100vh;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
}

Разобьём .about на 4 ячейки и расположим в них заголовок, фотографию, текст и социокнопки:

body {
  background: #000;
  color: #fff;
  display: flex;
  min-height: 100vh;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr; /* Две колонки, делящие свободное пространство в пропорции 77,5:22,5 */
  grid-template-rows: min-content min-content; /* Два ряда высотой по содержимому */
  gap: 20px 10px;
}

.about h1 {
  grid-area: 2 / 1; /* Сокращённая форма записи grid-row: 1; grid-column: 2 */
}

.about .photo {
  grid-area: 1 / 2; /* grid-row: 2; grid-column: 1 */
}

.about .text {
  grid-area: 1 / 1;
}

.about .social {
  grid-area: 2 / 2;
}

Наполируем раскладку. Отожмём строки к краям окна и поставим социокнопки на место:

body {
  background: #000;
  color: #fff;
  display: flex;
  min-height: 100vh;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between; /* Отожмём текст и фотографию к верхнему краю окна, а заголовок и социокнопки — к нижнему */
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end; /* Социокнопки отожмём в правый нижний угол */
  justify-self: end;
}

Настроим типографику. Зададим основной шрифт, стилизуем заголовок и текст:

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw; /* Пусть кегль заголовка зависит от ширины окна */
  line-height: .775; /* Уменьшаем интерлиньяж, чтобы попасть в макет */
  letter-spacing: -.05em; /* Уменьшаем расстояния между буквами */
}

.text {
  font-size: max(18px, 1.9vw); /* Пусть кегль текста также зависит от ширины окна, но с минимальным значением в 18 пикселей */
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}

Разобьём текст на 2 колонки:

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: 1fr 1fr; /* Две колонки, делящие свободное пространство в пропорции 1:1 */
  column-gap: 10px;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}

Ограничим текст по ширине и прижмём его к фотографии, чтобы получить узкие колонки, как в макете:

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
  max-width: max(280px, 50%);
  justify-self: end;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}

Вёрстку социокнопок оставим читателю. Она почти не будет отличаться от вёрстки таких же кнопок в подвале.

Давайте оста­но­вимся на минутку и взгля­нем на макет.

Рас­кладка и поря­док эле­мен­тов серьёзно отли­ча­ется между деск­то­пом и моби­лой. На деск­топе сна­чала идёт текст и фото­гра­фия, а затем заго­ло­вок. На мобиль­ных — фото­гра­фия, заго­ло­вок и текст. Только блок соци­окно­пок оста­ётся в конце стра­ницы в обеих вер­сиях. Больше того, на деск­топе текст огра­ни­чен по ширине и при­жат к фотографии.

Чтобы доба­вить мобиль­ную вер­сию, можно посту­пить как раньше — оста­вить суще­ству­ю­щие стили, счи­тая их общими, и допи­сать сти­лей для мобиль­ных в отдель­ном медиа­вы­ра­же­нии. В нашем слу­чае в сти­лях для мобиль­ных при­дётся отме­нить вырав­ни­ва­ние ячеек, снять огра­ни­че­ние мак­си­маль­ной ширины тек­ста и сбро­сить коор­ди­наты ячеек в грид‑кон­тей­нере. То есть боль­шая часть кода будет отме­нять стили, напи­сан­ные нами ранее. Чтобы не бороться с соб­ствен­ными сти­лями, лучше посту­пить по‑дру­гому — раз­де­лить суще­ству­ю­щие стили на общие, деск­топ­ные и мобильные.

Давайте остановимся на минутку и взглянем на макет.

Раскладка и порядок элементов серьёзно отличается между десктопом и мобилой. На десктопе сначала идёт текст и фотография, а затем заголовок. На мобильных — фотография, заголовок и текст. Только блок социокнопок остаётся в конце страницы в обеих версиях. Больше того, на десктопе текст ограничен по ширине и прижат к фотографии.

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

Давайте «рас­па­куем» код — выне­сем общее и отде­лим стили для деск­то­пов. Вот, на чём мы остановились.

Во всех вер­сиях .about дол­жен быть грид‑кон­тей­не­ром и зани­мать мини­мум 100% высоты окна. Также во всех вер­сиях текст раз­бит на две колонки.

Выне­сем эти стили отдельно.

Нако­нец завер­нём стили для рас­кладки в медиа­вы­ра­же­ние, чтобы они при­ме­ня­лись только на десктопах.

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
  max-width: max(280px, 50%);
  justify-self: end;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}
body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
}

.about .text {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
}


.about {
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;


.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
  max-width: max(280px, 50%);
  justify-self: end;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
}

.about .text {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
}

@media (width >= 768px) {
  .about {
    grid-template-columns: 77.5fr 22.5fr;
    grid-template-rows: min-content min-content;
    gap: 20px 10px;
    align-content: space-between;
  }

  .about h1 {
    grid-area: 2 / 1;
  }

  .about .photo {
    grid-area: 1 / 2;
  }

  .about .text {
    grid-area: 1 / 1;
    max-width: max(280px, 50%);
    justify-self: end;
  }

  .about .social {
    grid-area: 2 / 2;
    align-self: end;
    justify-self: end;
  }
}
body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;
}

.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
  max-width: max(280px, 50%);
  justify-self: end;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}
body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
}

.about .text {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
}


.about {
  grid-template-columns: 77.5fr 22.5fr;
  grid-template-rows: min-content min-content;
  gap: 20px 10px;
  align-content: space-between;
}

.about h1 {
  grid-area: 2 / 1;


.about .photo {
  grid-area: 1 / 2;
}

.about .text {
  grid-area: 1 / 1;
  max-width: max(280px, 50%);
  justify-self: end;
}

.about .social {
  grid-area: 2 / 2;
  align-self: end;
  justify-self: end;
}

body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
}

.about .text {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
}

@media (width >= 768px) {
  .about {
    grid-template-columns: 77.5fr 22.5fr;
    grid-template-rows: min-content min-content;
    gap: 20px 10px;
    align-content: space-between;
  }

  .about h1 {
    grid-area: 2 / 1;
  }

  .about .photo {
    grid-area: 1 / 2;
  }

  .about .text {
    grid-area: 1 / 1;
    max-width: max(280px, 50%);
    justify-self: end;
  }

  .about .social {
    grid-area: 2 / 2;
    align-self: end;
    justify-self: end;
  }
}

Давайте «распакуем» код — вынесем общее и отделим стили для десктопов. Вот, на чём мы остановились.

Во всех версиях .about должен быть грид‑контейнером и занимать минимум 100% высоты окна. Также во всех версиях текст разбит на две колонки.

Вынесем эти стили отдельно.

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

Доба­вим мобиль­ную вер­сию. Зафик­си­руем поля стра­ницы, сде­лаем одну колонку и мак­си­мум рядов по содер­жи­мому. Поря­док эле­мен­тов не меняем.

@media (width < 768px) {
  .layoutContainer {
    padding: 20px 20px 30px; /* Меняем резиновые поля на фиксированные */
  }

  .about {
    grid-template-columns: 1fr; /* Одна колонка */
    grid-auto-flow: row; /* Столько рядов, сколько нужно, с высотой по содержимому */
    grid-auto-rows: max-content;
    gap: 20px;
  }

  .about .photo {
    width: 100%; /* Тянем фотографию на всю ширину экрана */
  }

  .about h1 {
    font-size: 14.5vw; /* Увеличиваем кегль заголовка */
  }
  
  .about .text > :last-child {
    order: -1; /* На мобильных первым идёт второй абзац */
  }
}

Роман Жалялов, дизайнер интерфейсов

Учусь на второй ступени Школы дизайнеров Бюро Горбунова.

Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».

Добавим мобильную версию. Зафиксируем поля страницы, сделаем одну колонку и максимум рядов по содержимому. Порядок элементов не меняем.

@media (width < 768px) {
  .layoutContainer {
    padding: 20px 20px 30px; /* Меняем резиновые поля на фиксированные */
  }

  .about {
    grid-template-columns: 1fr; /* Одна колонка */
    grid-auto-flow: row; /* Столько рядов, сколько нужно, с высотой по содержимому */
    grid-auto-rows: max-content;
    gap: 20px;
  }

  .about .photo {
    width: 100%; /* Тянем фотографию на всю ширину экрана */
  }

  .about h1 {
    font-size: 14.5vw; /* Увеличиваем кегль заголовка */
  }
  
  .about .text > :last-child {
    order: -1; /* На мобильных первым идёт второй абзац */
  }
}

Роман Жалялов, дизайнер интерфейсов

Учусь на второй ступени Школы дизайнеров Бюро Горбунова.

Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».

Роман Жалялов, дизайнер интерфейсов

Учусь на второй ступени Школы дизайнеров Бюро Горбунова.

Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».

Вот что, в итоге, у нас получилось:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Роман Жалялов, дизайнер интерфейсов</title>
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="layoutContainer">
    <section class="about">
      <img class="photo" src="images/me.jpg">

      <h1>Роман Жалялов, дизайнер интерфейсов</h1>

      <div class="text">
        <p>Учусь на второй ступени Школы дизайнеров Бюро Горбунова.</p>

        <p>Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».</p>
      </div>

      <footer class="social">
        <a><svg width="46" height="40" viewBox="0 0 25 22" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.55 9.598c7.077-3.253 16.236-7.218 17.5-7.767C23.367.395 24.384.67 23.877 3.851c-.365 2.287-1.416 9.857-2.254 14.569-.497 2.794-1.613 3.125-3.367 1.916-.843-.582-5.101-3.523-6.025-4.213-.844-.63-2.007-1.387-.548-2.878.519-.531 3.923-3.926 6.574-6.569.348-.347-.089-.917-.49-.638a3088.93 3088.93 0 0 0-9.16 6.36c-.953.676-1.868.986-3.511.493-1.241-.373-2.453-.817-2.925-.986-1.817-.652-1.386-1.496.379-2.307Z" fill="currentColor"></path></svg></a>
        <a><svg viewBox="1 1.203 28.002 17.595" width="54.465" height="33" xmlns="http://www.w3.org/2000/svg"><path d="M9.144 1.203c.808 0 1.554.062 2.238.249a4.855 4.855 0 0 1 1.741.684c.498.311.87.746 1.119 1.306s.373 1.243.373 1.989c0 .87-.187 1.616-.622 2.176-.373.56-.995 1.057-1.741 1.43 1.057.311 1.865.87 2.362 1.616a4.9 4.9 0 0 1 .808 2.735c0 .87-.187 1.616-.497 2.238a4.123 4.123 0 0 1-1.368 1.554 6.429 6.429 0 0 1-1.989.87c-.746.187-1.492.311-2.238.311H1V1.203h8.144Zm-.497 6.963c.684 0 1.243-.187 1.679-.497.436-.31.622-.87.622-1.554 0-.373-.062-.746-.187-.995s-.311-.435-.56-.622c-.249-.124-.497-.249-.808-.311-.311-.062-.622-.062-.995-.062H4.792v4.041h3.855Zm.187 7.336c.373 0 .746-.062 1.057-.124.311-.062.622-.187.87-.373.249-.187.435-.373.622-.684.124-.311.249-.684.249-1.119 0-.87-.249-1.492-.746-1.927-.497-.373-1.181-.56-1.989-.56H4.792v4.787h4.042Zm11.998-.062c.497.497 1.243.746 2.238.746.684 0 1.306-.187 1.803-.497.497-.373.808-.746.933-1.119h3.046c-.497 1.492-1.243 2.549-2.238 3.233-.995.622-2.176.995-3.606.995a7.324 7.324 0 0 1-2.673-.497 4.953 4.953 0 0 1-1.989-1.368 4.92 4.92 0 0 1-1.243-2.052c-.311-.808-.435-1.679-.435-2.673 0-.933.124-1.803.435-2.611a6.706 6.706 0 0 1 1.306-2.114c.56-.56 1.243-1.057 1.989-1.368a6.971 6.971 0 0 1 2.611-.497c1.057 0 1.989.187 2.798.622.809.435 1.43.933 1.927 1.679.497.684.87 1.492 1.119 2.362.124.87.187 1.741.124 2.735h-9.014c0 .995.373 1.927.87 2.425l-.001-.001Zm3.917-6.528c-.435-.435-1.119-.684-1.927-.684-.56 0-.995.124-1.368.311-.373.187-.622.435-.87.684-.249.249-.373.56-.435.87-.062.31-.124.56-.124.808h5.595c-.124-.933-.436-1.554-.871-1.989Zm-5.471-6.528h6.963v1.679h-6.963V2.384Z" fill="currentColor"></path></svg></a>
      </footer>
    </section>
  </div>
</body>
</html>
body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
}

.about .text {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
}

.social {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end;
  column-gap: 15px;
}

.social svg {
  max-height: 24px;
  width: auto;
}

@media (width >= 768px) {
  .about {
    grid-template-columns: 77.5fr 22.5fr;
    grid-template-rows: min-content min-content;
    gap: 20px 10px;
    align-content: space-between;
  }

  .about h1 {
    grid-area: 2 / 1;
  }

  .about .photo {
    grid-area: 1 / 2;
  }

  .about .text {
    grid-area: 1 / 1;
    max-width: max(280px, 50%);
    justify-self: end;
  }

  .about .social {
    grid-area: 2 / 2;
    align-self: end;
    justify-self: end;
  }
}


@media (width < 768px) {
  .layoutContainer {
    padding: 20px 20px 30px;
  }

  .about {
    grid-template-columns: 1fr;
    grid-auto-flow: row;
    grid-auto-rows: max-content;
    gap: 20px;
  }

  .about .photo {
    width: 100%;
  }

  .about h1 {
    font-size: 14.5vw;
  }

  .about .text > :last-child {
    order: -1;
  }
}

Роман Жалялов, дизайнер интерфейсов

Учусь на второй ступени Школы дизайнеров Бюро Горбунова.

Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».

Вот что, в итоге, у нас получилось:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Роман Жалялов, дизайнер интерфейсов</title>
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="layoutContainer">
    <section class="about">
      <img class="photo" src="images/me.jpg">

      <h1>Роман Жалялов, дизайнер интерфейсов</h1>

      <div class="text">
        <p>Учусь на второй ступени Школы дизайнеров Бюро Горбунова.</p>

        <p>Проектирую сайты и мобильные приложения, использую ФФФ, «травлю леску».</p>
      </div>

      <footer class="social">
        <a><svg width="46" height="40" viewBox="0 0 25 22" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.55 9.598c7.077-3.253 16.236-7.218 17.5-7.767C23.367.395 24.384.67 23.877 3.851c-.365 2.287-1.416 9.857-2.254 14.569-.497 2.794-1.613 3.125-3.367 1.916-.843-.582-5.101-3.523-6.025-4.213-.844-.63-2.007-1.387-.548-2.878.519-.531 3.923-3.926 6.574-6.569.348-.347-.089-.917-.49-.638a3088.93 3088.93 0 0 0-9.16 6.36c-.953.676-1.868.986-3.511.493-1.241-.373-2.453-.817-2.925-.986-1.817-.652-1.386-1.496.379-2.307Z" fill="currentColor"></path></svg></a>
        <a><svg viewBox="1 1.203 28.002 17.595" width="54.465" height="33" xmlns="http://www.w3.org/2000/svg"><path d="M9.144 1.203c.808 0 1.554.062 2.238.249a4.855 4.855 0 0 1 1.741.684c.498.311.87.746 1.119 1.306s.373 1.243.373 1.989c0 .87-.187 1.616-.622 2.176-.373.56-.995 1.057-1.741 1.43 1.057.311 1.865.87 2.362 1.616a4.9 4.9 0 0 1 .808 2.735c0 .87-.187 1.616-.497 2.238a4.123 4.123 0 0 1-1.368 1.554 6.429 6.429 0 0 1-1.989.87c-.746.187-1.492.311-2.238.311H1V1.203h8.144Zm-.497 6.963c.684 0 1.243-.187 1.679-.497.436-.31.622-.87.622-1.554 0-.373-.062-.746-.187-.995s-.311-.435-.56-.622c-.249-.124-.497-.249-.808-.311-.311-.062-.622-.062-.995-.062H4.792v4.041h3.855Zm.187 7.336c.373 0 .746-.062 1.057-.124.311-.062.622-.187.87-.373.249-.187.435-.373.622-.684.124-.311.249-.684.249-1.119 0-.87-.249-1.492-.746-1.927-.497-.373-1.181-.56-1.989-.56H4.792v4.787h4.042Zm11.998-.062c.497.497 1.243.746 2.238.746.684 0 1.306-.187 1.803-.497.497-.373.808-.746.933-1.119h3.046c-.497 1.492-1.243 2.549-2.238 3.233-.995.622-2.176.995-3.606.995a7.324 7.324 0 0 1-2.673-.497 4.953 4.953 0 0 1-1.989-1.368 4.92 4.92 0 0 1-1.243-2.052c-.311-.808-.435-1.679-.435-2.673 0-.933.124-1.803.435-2.611a6.706 6.706 0 0 1 1.306-2.114c.56-.56 1.243-1.057 1.989-1.368a6.971 6.971 0 0 1 2.611-.497c1.057 0 1.989.187 2.798.622.809.435 1.43.933 1.927 1.679.497.684.87 1.492 1.119 2.362.124.87.187 1.741.124 2.735h-9.014c0 .995.373 1.927.87 2.425l-.001-.001Zm3.917-6.528c-.435-.435-1.119-.684-1.927-.684-.56 0-.995.124-1.368.311-.373.187-.622.435-.87.684-.249.249-.373.56-.435.87-.062.31-.124.56-.124.808h5.595c-.124-.933-.436-1.554-.871-1.989Zm-5.471-6.528h6.963v1.679h-6.963V2.384Z" fill="currentColor"></path></svg></a>
      </footer>
    </section>
  </div>
</body>
</html>
body {
  background: #000;
  color: #fff;
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  display: flex;
  min-height: 100vh;
}

h1 {
  font-size: 11vw;
  line-height: .775;
  letter-spacing: -.05em;
}

.text {
  font-size: max(18px, 1.9vw);
  line-height: 1;
}

.layoutContainer {
  padding: 1.75% 2.625% 3.5%;
  flex: 1;
}

.about {
  min-height: 100%;
  display: grid;
}

.about .text {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 10px;
}

.social {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  justify-content: end;
  column-gap: 15px;
}

.social svg {
  max-height: 24px;
  width: auto;
}

@media (width >= 768px) {
  .about {
    grid-template-columns: 77.5fr 22.5fr;
    grid-template-rows: min-content min-content;
    gap: 20px 10px;
    align-content: space-between;
  }

  .about h1 {
    grid-area: 2 / 1;
  }

  .about .photo {
    grid-area: 1 / 2;
  }

  .about .text {
    grid-area: 1 / 1;
    max-width: max(280px, 50%);
    justify-self: end;
  }

  .about .social {
    grid-area: 2 / 2;
    align-self: end;
    justify-self: end;
  }
}


@media (width < 768px) {
  .layoutContainer {
    padding: 20px 20px 30px;
  }

  .about {
    grid-template-columns: 1fr;
    grid-auto-flow: row;
    grid-auto-rows: max-content;
    gap: 20px;
  }

  .about .photo {
    width: 100%;
  }

  .about h1 {
    font-size: 14.5vw;
  }

  .about .text > :last-child {
    order: -1;
  }
}

Портфолио

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

Чаще всего встре­ча­ются харак­тер­ные виды вёрстки порт­фо­лио: «лента», «плитка» и «рубрикатор».

Лента состоит из типо­вых моду­лей — кар­то­чек про­ек­тов. Чаще всего в хро­но­ло­ги­че­ском порядке, где новое сверху.

Плитка пред­став­ляет собой модуль­ную кладку из пря­мо­уголь­ни­ков, ино­гда раз­ных раз­ме­ров и про­пор­ций. В отли­чие от ленты, модули рас­по­ло­жены не друг под дру­гом, а плотно упа­ко­ваны по рядам — слева направо, сверху вниз.

Руб­ри­ка­тор состав­лен из моду­лей с раз­ными фор­ма­тами по руб­ри­кам: напри­мер послед­ний круп­ный про­ект, плитка, ново­сти или лого­типы клиентов.

Артём Горбунов. Типографика и вёрстка. Главная страница

Портфолио

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

Чаще всего встречаются характерные виды вёрстки портфолио: «лента», «плитка» и «рубрикатор».

Лента состоит из типовых модулей — карточек проектов. Чаще всего в хронологическом порядке, где новое сверху.

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

Рубрикатор составлен из модулей с разными форматами по рубрикам: например последний крупный проект, плитка, новости или логотипы клиентов.

Артём Горбунов. Типографика и вёрстка. Главная страница

Раз­бе­рём вёрстку самой попу­ляр­ной рас­кладки порт­фо­лио — вёрстку плит­кой. Счи­таем, что в нашем порт­фо­лио сетка на 4 колонки с меж­ко­лон­ни­ком в 20 пик­се­лей. Сосре­до­то­чимся только на рас­кладке, опу­стив типо­гра­фику и стили для сброса.

Нач­нём с раз­метки, не забыв об обёртке для всей стра­ницы. Каж­дый про­ект — это див с клас­сом project и моди­фи­ка­то­ром раз­мера: sizeL — на все четыре колонки, sizeM — на две колонки, sizeS — на одну колонку. Собе­рём несколько ком­би­на­ций из них:

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL"></div>

    <div class="project sizeS"></div>
    <div class="project sizeM"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeM"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
  </section>
</div>

Вклю­чим гриды, зада­дим сетку из четы­рёх коло­нок и моди­фи­ка­торы ширины плиток:

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh; /* Мы задаём высоту ради примера, чтобы ячейки не схлопывались. В реальной жизни высота будет определяться пропорциями содержимого ячеек */
  overflow: hidden;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

Разберём вёрстку самой популярной раскладки портфолио — вёрстку плиткой. Считаем, что в нашем портфолио сетка на 4 колонки с межколонником в 20 пикселей. Сосредоточимся только на раскладке, опустив типографику и стили для сброса.

Начнём с разметки, не забыв об обёртке для всей страницы. Каждый проект — это див с классом project и модификатором размера: sizeL — на все четыре колонки, sizeM — на две колонки, sizeS — на одну колонку. Соберём несколько комбинаций из них:

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL"></div>

    <div class="project sizeS"></div>
    <div class="project sizeM"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeM"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
  </section>
</div>

Включим гриды, зададим сетку из четырёх колонок и модификаторы ширины плиток:

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh; /* Мы задаём высоту ради примера, чтобы ячейки не схлопывались. В реальной жизни высота будет определяться пропорциями содержимого ячеек */
  overflow: hidden;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

Доба­вим мобиль­ную вер­сию «в лоб» — перей­дём на две колонки:

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh;
  overflow: hidden;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr; /* На мобильных сетка из двух колонок */
    grid-auto-flow: dense; /* Просим гриды расставлять проекты по ячейкам «без дыр», пусть даже для этого придётся поменять порядок ячеек */
  }

  .project.sizeL {
    grid-column: auto / span 2;
  }
}

Добавим мобильную версию «в лоб» — перейдём на две колонки:

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh;
  overflow: hidden;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr; /* На мобильных сетка из двух колонок */
    grid-auto-flow: dense; /* Просим гриды расставлять проекты по ячейкам «без дыр», пусть даже для этого придётся поменять порядок ячеек */
  }

  .project.sizeL {
    grid-column: auto / span 2;
  }
}

Мобиль­ная вер­сия «в лоб» даёт дубли — повто­ря­ю­щи­еся этажи с оди­на­ко­вой рас­клад­кой. Чтобы изба­виться от этого, вве­дём моди­фи­ка­торы ширины для мобиль­ной версии:

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh;
  overflow: hidden;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: dense;
  }
  
  .project.mobileSizeS,
  .project.mobileSizeM {
    grid-column: auto / span 1;
  }

  .project.l,
  .project.mobileSizeL {
    grid-column: auto / span 2;
  }
}

И рас­ста­вим их, чере­дуя ритм один про­ект, два проекта:

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL mobileSizeL"></div>
    
    <div class="project sizeS mobileSizeS"></div>
    <div class="project sizeM mobileSizeS"></div>
    
    <div class="project sizeS mobileSizeL"></div>
    
    <div class="project sizeM mobileSizeS"></div>
    <div class="project sizeM mobileSizeS"></div>
    
    <div class="project sizeS mobileSizeL"></div>
    
    <div class="project sizeS mobileSizeS"></div>
    <div class="project sizeM mobileSizeS"></div>

    <div class="project sizeS mobileSizeL"></div>
    
    <div class="project sizeM mobileSizeS"></div>
    <div class="project sizeS mobileSizeS"></div>

    <div class="project sizeM mobileSizeL"></div>
    
    <div class="project sizeS mobileSizeS"></div>
    <div class="project sizeS mobileSizeS"></div>

    <div class="project sizeM mobileSizeL"></div>
    
    <div class="project sizeM mobileSizeS"></div>
    <div class="project sizeS mobileSizeS"></div>
    
    <div class="project sizeS mobileSizeL"></div>
    
    <div class="project sizeS mobileSizeS"></div>
    <div class="project sizeS mobileSizeS"></div>
  </section>
</div>

Мобильная версия «в лоб» даёт дубли — повторяющиеся этажи с одинаковой раскладкой. Чтобы избавиться от этого, введём модификаторы ширины для мобильной версии:

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh;
  overflow: hidden;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: dense;
  }
  
  .project.mobileSizeS,
  .project.mobileSizeM {
    grid-column: auto / span 1;
  }

  .project.l,
  .project.mobileSizeL {
    grid-column: auto / span 2;
  }
}

И расставим их, чередуя ритм один проект, два проекта:

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL mobileSizeL"></div>
    
    <div class="project sizeS mobileSizeS"></div>
    <div class="project sizeM mobileSizeS"></div>
    
    <div class="project sizeS mobileSizeL"></div>
    
    <div class="project sizeM mobileSizeS"></div>
    <div class="project sizeM mobileSizeS"></div>
    
    <div class="project sizeS mobileSizeL"></div>
    
    <div class="project sizeS mobileSizeS"></div>
    <div class="project sizeM mobileSizeS"></div>

    <div class="project sizeS mobileSizeL"></div>
    
    <div class="project sizeM mobileSizeS"></div>
    <div class="project sizeS mobileSizeS"></div>

    <div class="project sizeM mobileSizeL"></div>
    
    <div class="project sizeS mobileSizeS"></div>
    <div class="project sizeS mobileSizeS"></div>

    <div class="project sizeM mobileSizeL"></div>
    
    <div class="project sizeM mobileSizeS"></div>
    <div class="project sizeS mobileSizeS"></div>
    
    <div class="project sizeS mobileSizeL"></div>
    
    <div class="project sizeS mobileSizeS"></div>
    <div class="project sizeS mobileSizeS"></div>
  </section>
</div>

Чтобы не рас­став­лять классы мобиль­ной рас­кладки вруч­ную, авто­ма­ти­зи­руем их назна­че­ние с помо­щью неболь­шого скрипта.

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL"></div>

    <div class="project sizeS"></div>
    <div class="project sizeM"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeM"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
  </section>
</div>

<script>
  const MOBILE_LAYOUT = ['L', 'S', 'S'] // Вариации, которые будем чередовать на мобильных
  const projects = document.querySelectorAll('.project') // Находим все проекты на странице

  projects.forEach((project, projectIndex) => {
    const layoutClass = MOBILE_LAYOUT[projectIndex % MOBILE_LAYOUT.length] // Последовательно выбираем текущую вариацию: L → S → S → L → S → S и так далее
    const mobileClass = `mobileSize${layoutClass}` // Собираем правильный класс

    project.classList.add(mobileClass) // И добавляем его проекту
  })
</script>

Чтобы не расставлять классы мобильной раскладки вручную, автоматизируем их назначение с помощью небольшого скрипта.

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL"></div>

    <div class="project sizeS"></div>
    <div class="project sizeM"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeM"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>

    <div class="project sizeM"></div>
    <div class="project sizeM"></div>

    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
    <div class="project sizeS"></div>
  </section>
</div>

<script>
  const MOBILE_LAYOUT = ['L', 'S', 'S'] // Вариации, которые будем чередовать на мобильных
  const projects = document.querySelectorAll('.project') // Находим все проекты на странице

  projects.forEach((project, projectIndex) => {
    const layoutClass = MOBILE_LAYOUT[projectIndex % MOBILE_LAYOUT.length] // Последовательно выбираем текущую вариацию: L → S → S → L → S → S и так далее
    const mobileClass = `mobileSize${layoutClass}` // Собираем правильный класс

    project.classList.add(mobileClass) // И добавляем его проекту
  })
</script>

Когда рас­кладка готова, запол­ним ячейки кар­точ­ками про­ек­тов. Каж­дая кар­точка — это ссылка, в кото­рую вло­жена иллю­стра­ция и под­пись про­екта. Отра­зим это в разметке:

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL">
      <a href="/projects/maximilyahov-identity/"> <!-- Каждому проекту — отдельную страницу -->
        <div class="project-media"> <!-- Контейнер для иллюстрации проекта — картинки, гифки или видео -->
          <img src="/projects/maximilyahov-identity/files/cover.jpg"> <!-- Храним файлы, связанные с проектом, рядом с самим проектом -->
        </div>

        <div class="project-caption"> <!-- Считаем, что стили для подписей и базовая типографика уже есть -->
          Максим Ильяхов десять лет учит интернет писать. Теперь у него есть фирстиль
        </div>
      </a>
    </div>

    <div class="project sizeS">
      <a href="/projects/worldchess-font/">
        <div class="project-media">
          <img src="/projects/worldchess-font/files/cover.jpg">
        </div>
        <div class="project-caption">
          Шрифт для цен Ворлд Чесс
        </div>
      </a>
    </div>
    
    <div class="project sizeM">
      <a href="/projects/worldchess-2/">
        <div class="project-media">
          <img src="/projects/worldchess-2/files/cover.jpg">
        </div>
        <div class="project-caption">
          Вторая версия главной страницы Ворлд Чесса
        </div>
      </a>
    </div>
    
    <div class="project sizeS">
      <a href="/projects/worldchess-broadcasts/">
        <div class="project-media">
          <img src="/projects/worldchess-broadcasts/files/cover.jpg">
        </div>
        <div class="project-caption">
          Ворлд Чесс ведёт трансляции шахматных турниров
        </div>
      </a>
    </div>
  </section>
</div>

Убе­рём мини­маль­ную высоту ячеек, чтобы их высота опре­де­ля­лась содер­жи­мым, и сти­ли­зуем карточки:

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh;
  overflow: hidden;
}

.project-media {
  margin: -10px -10px 0; /* Вытягиваем иллюстрацию проекта на поля ячейки */
  aspect-ratio: 1.91 / 1; /* Резервируем место под картинки, опираясь на их пропорции, чтобы страница не дёргалась при загрузке */
}

.project-caption { /* Настраиваем подписи */
  margin-top: .5em;
  font-size: 12px;
  line-height: 1.2;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: dense;
  }

  .project.mobileSizeS,
  .project.mobileSizeM {
    grid-column: auto / span 1;
  }

  .project.sizeL,
  .project.mobileSizeL {
    grid-column: auto / span 2;
  }
}

Когда раскладка готова, заполним ячейки карточками проектов. Каждая карточка — это ссылка, в которую вложена иллюстрация и подпись проекта. Отразим это в разметке:

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL">
      <a href="/projects/maximilyahov-identity/"> <!-- Каждому проекту — отдельную страницу -->
        <div class="project-media"> <!-- Контейнер для иллюстрации проекта — картинки, гифки или видео -->
          <img src="/projects/maximilyahov-identity/files/cover.jpg"> <!-- Храним файлы, связанные с проектом, рядом с самим проектом -->
        </div>

        <div class="project-caption"> <!-- Считаем, что стили для подписей и базовая типографика уже есть -->
          Максим Ильяхов десять лет учит интернет писать. Теперь у него есть фирстиль
        </div>
      </a>
    </div>

    <div class="project sizeS">
      <a href="/projects/worldchess-font/">
        <div class="project-media">
          <img src="/projects/worldchess-font/files/cover.jpg">
        </div>
        <div class="project-caption">
          Шрифт для цен Ворлд Чесс
        </div>
      </a>
    </div>
    
    <div class="project sizeM">
      <a href="/projects/worldchess-2/">
        <div class="project-media">
          <img src="/projects/worldchess-2/files/cover.jpg">
        </div>
        <div class="project-caption">
          Вторая версия главной страницы Ворлд Чесса
        </div>
      </a>
    </div>
    
    <div class="project sizeS">
      <a href="/projects/worldchess-broadcasts/">
        <div class="project-media">
          <img src="/projects/worldchess-broadcasts/files/cover.jpg">
        </div>
        <div class="project-caption">
          Ворлд Чесс ведёт трансляции шахматных турниров
        </div>
      </a>
    </div>
  </section>
</div>

Уберём минимальную высоту ячеек, чтобы их высота определялась содержимым, и стилизуем карточки:

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh;
  overflow: hidden;
}

.project-media {
  margin: -10px -10px 0; /* Вытягиваем иллюстрацию проекта на поля ячейки */
  aspect-ratio: 1.91 / 1; /* Резервируем место под картинки, опираясь на их пропорции, чтобы страница не дёргалась при загрузке */
}

.project-caption { /* Настраиваем подписи */
  margin-top: .5em;
  font-size: 12px;
  line-height: 1.2;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: dense;
  }

  .project.mobileSizeS,
  .project.mobileSizeM {
    grid-column: auto / span 1;
  }

  .project.sizeL,
  .project.mobileSizeL {
    grid-column: auto / span 2;
  }
}

Наве­дём поря­док в ссыл­ках. Завер­нём в <u> те части подпи­сей, что должны выгля­деть как ссылки — про­екты или клиенты:

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL">
      <a href="/projects/maximilyahov-identity/">
        <div class="project-media">
          <img src="/projects/maximilyahov-identity/files/cover.jpg">
        </div>

        <div class="project-caption">
          <u>Максим Ильяхов</u> десять лет учит интернет писать. Теперь у него есть фирстиль
        </div>
      </a>
    </div>

    <div class="project sizeS">
      <a href="/projects/worldchess-font/">
        <div class="project-media">
          <img src="/projects/worldchess-font/files/cover.jpg">
        </div>
        <div class="project-caption">
          Шрифт для цен Ворлд Чесс
        </div>
      </a>
    </div>
    
    <div class="project sizeM">
      <a href="/projects/worldchess-2/">
        <div class="project-media">
          <img src="/projects/worldchess-2/files/cover.jpg">
        </div>
        <div class="project-caption">
          Вторая версия главной страницы Ворлд Чесса
        </div>
      </a>
    </div>
    
    <div class="project sizeS">
      <a href="/projects/worldchess-broadcasts/">
        <div class="project-media">
          <img src="/projects/worldchess-broadcasts/files/cover.jpg">
        </div>
        <div class="project-caption">
          Ворлд Чесс ведёт трансляции шахматных турниров
        </div>
      </a>
    </div>
  </section>
</div>

И сти­ли­зуем их:

:root {
  --textColor: #000; /* Вводим переменные для цвета текста и ссылок */
  --linkColor: #00aff0;
  --keyColor: #ff5e00;
}

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
}

a,
a u {
  text-decoration: underline; /* Настраиваем тонкие полупрозрачные подчёркивания у ссылок */
  text-decoration-thickness: 1px; /* Задаём толщину подчёркивания */
  text-underline-offset: .175em; /* Задаём сдвиг подчёркивания */
  text-decoration-color: color-mix(in srgb, currentColor, transparent 80%); /* Для подчёркивания используем текущий цвет текста с прозрачностью */
  text-decoration-skip: none; /* Запрещаем подчёркиванию пропускать выносные элементы букв */
  text-decoration-skip-ink: none;
  color: var(--linkColor);
}

a:has(u) {
  text-decoration: none; /* Если внутри ссылки есть тег <u>, то подчёркиваем только его */
}

a:hover,
a:hover u {
  color: var(--keyColor);
  text-decoration-color: currentColor; /* Когда курсор над ссылкой, подчёркиваем и перекрашиваем её в ключевой цвет страницы */
}

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  overflow: hidden;
}

.project a,
.project a:hover {
  color: var(--textColor); /* Перекрашиваем подписи в цвет текста */
}

.project-media {
  margin: -10px -10px 0;
  aspect-ratio: 1.91 / 1;
}

.project-caption {
  margin-top: .5em;
  font-size: 12px;
  line-height: 1.2;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: dense;
  }

  .project.mobileSizeS,
  .project.mobileSizeM {
    grid-column: auto / span 1;
  }

  .project.sizeL,
  .project.mobileSizeL {
    grid-column: auto / span 2;
  }
}

Наведём порядок в ссылках. Завернём в <u> те части подписей, что должны выглядеть как ссылки — проекты или клиенты:

<div class="layoutContainer">
  <section class="projects">
    <div class="project sizeL">
      <a href="/projects/maximilyahov-identity/">
        <div class="project-media">
          <img src="/projects/maximilyahov-identity/files/cover.jpg">
        </div>

        <div class="project-caption">
          <u>Максим Ильяхов</u> десять лет учит интернет писать. Теперь у него есть фирстиль
        </div>
      </a>
    </div>

    <div class="project sizeS">
      <a href="/projects/worldchess-font/">
        <div class="project-media">
          <img src="/projects/worldchess-font/files/cover.jpg">
        </div>
        <div class="project-caption">
          Шрифт для цен Ворлд Чесс
        </div>
      </a>
    </div>
    
    <div class="project sizeM">
      <a href="/projects/worldchess-2/">
        <div class="project-media">
          <img src="/projects/worldchess-2/files/cover.jpg">
        </div>
        <div class="project-caption">
          Вторая версия главной страницы Ворлд Чесса
        </div>
      </a>
    </div>
    
    <div class="project sizeS">
      <a href="/projects/worldchess-broadcasts/">
        <div class="project-media">
          <img src="/projects/worldchess-broadcasts/files/cover.jpg">
        </div>
        <div class="project-caption">
          Ворлд Чесс ведёт трансляции шахматных турниров
        </div>
      </a>
    </div>
  </section>
</div>

И стилизуем их:

:root {
  --textColor: #000; /* Вводим переменные для цвета текста и ссылок */
  --linkColor: #00aff0;
  --keyColor: #ff5e00;
}

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
}

a,
a u {
  text-decoration: underline; /* Настраиваем тонкие полупрозрачные подчёркивания у ссылок */
  text-decoration-thickness: 1px; /* Задаём толщину подчёркивания */
  text-underline-offset: .175em; /* Задаём сдвиг подчёркивания */
  text-decoration-color: color-mix(in srgb, currentColor, transparent 80%); /* Для подчёркивания используем текущий цвет текста с прозрачностью */
  text-decoration-skip: none; /* Запрещаем подчёркиванию пропускать выносные элементы букв */
  text-decoration-skip-ink: none;
  color: var(--linkColor);
}

a:has(u) {
  text-decoration: none; /* Если внутри ссылки есть тег <u>, то подчёркиваем только его */
}

a:hover,
a:hover u {
  color: var(--keyColor);
  text-decoration-color: currentColor; /* Когда курсор над ссылкой, подчёркиваем и перекрашиваем её в ключевой цвет страницы */
}

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  overflow: hidden;
}

.project a,
.project a:hover {
  color: var(--textColor); /* Перекрашиваем подписи в цвет текста */
}

.project-media {
  margin: -10px -10px 0;
  aspect-ratio: 1.91 / 1;
}

.project-caption {
  margin-top: .5em;
  font-size: 12px;
  line-height: 1.2;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: dense;
  }

  .project.mobileSizeS,
  .project.mobileSizeM {
    grid-column: auto / span 1;
  }

  .project.sizeL,
  .project.mobileSizeL {
    grid-column: auto / span 2;
  }
}

Доба­вим ещё ряд и собе­рём всё вместе:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Портфолио Романа</title>
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="layoutContainer">
    <section class="projects">
      <div class="project sizeL">
        <a href="/projects/maximilyahov-identity/">
          <div class="project-media">
            <img src="/projects/maximilyahov-identity/files/cover.jpg">
          </div>

          <div class="project-caption">
            <u>Максим Ильяхов</u> десять лет учит интернет писать. Теперь у него есть фирстиль
          </div>
        </a>
      </div>

      <div class="project sizeS">
        <a href="/projects/worldchess-font/">
          <div class="project-media">
            <img src="/projects/worldchess-font/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Шрифт для цен</u> Ворлд Чесс
          </div>
        </a>
      </div>

      <div class="project sizeM">
        <a href="/projects/worldchess-2/">
          <div class="project-media">
            <img src="/projects/worldchess-2/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Вторая версия главной страницы</u> Ворлд Чесса
          </div>
        </a>
      </div>

      <div class="project sizeS">
        <a href="/projects/worldchess-broadcasts/">
          <div class="project-media">
            <img src="/projects/worldchess-broadcasts/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Ворлд Чесс</u> ведёт трансляции шахматных турниров
          </div>
        </a>
      </div>

      <div class="project sizeM">
        <a href="/projects/vashrepetitor-logo/">
          <div class="project-media">
            <img src="/projects/vashrepetitor-logo/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Логотип</u> «Вашего репетитора» — сервиса подбора репетиторов
          </div>
        </a>
      </div>

      <div class="project sizeM">
        <a href="/projects/mailganer/">
          <div class="project-media">
            <img src="/projects/mailganer/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Сайт Мейлганера</u> — кросс-канальной платформы маркетинга
          </div>
        </a>
      </div>
    </section>
  </div>
  
  <script>
    const MOBILE_LAYOUT = ['L', 'S', 'S']

    document
      .querySelectorAll('.project')
      .forEach((project, index) => {
        const layoutClass = MOBILE_LAYOUT[index % MOBILE_LAYOUT.length]
        const mobileClass = `mobileSize${layoutClass}`

        project
          .classList
          .add(mobileClass)
      })
  </script>
</body>
</html>
:root {
  --textColor: #000;
  --linkColor: #00aff0;
  --keyColor: #ff5e00;
}

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
}

a,
a u {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .175em;
  text-decoration-color: color-mix(in srgb, currentColor, transparent 80%);
  text-decoration-skip: none;
  text-decoration-skip-ink: none;
  color: var(--linkColor);
}

a:has(u) {
  text-decoration: none;
}

a:hover,
a:hover u {
  color: var(--keyColor);
  text-decoration-color: currentColor;
}

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh;
  overflow: hidden;
}

.project a,
.project a:hover {
  color: var(--textColor);
}

.project-media {
  margin: -10px -10px 0;
  aspect-ratio: 1.91 / 1;
}

.project-caption {
  margin-top: .5em;
  font-size: 12px;
  line-height: 1.2;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: dense;
  }

  .project.mobileSizeS,
  .project.mobileSizeM {
    grid-column: auto / span 1;
  }

  .project.sizeL,
  .project.mobileSizeL {
    grid-column: auto / span 2;
  }
}

Добавим ещё ряд и соберём всё вместе:

<!DOCTYPE html>
<html lang="ru">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Портфолио Романа</title>
  <link rel="stylesheet" href="reset.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="layoutContainer">
    <section class="projects">
      <div class="project sizeL">
        <a href="/projects/maximilyahov-identity/">
          <div class="project-media">
            <img src="/projects/maximilyahov-identity/files/cover.jpg">
          </div>

          <div class="project-caption">
            <u>Максим Ильяхов</u> десять лет учит интернет писать. Теперь у него есть фирстиль
          </div>
        </a>
      </div>

      <div class="project sizeS">
        <a href="/projects/worldchess-font/">
          <div class="project-media">
            <img src="/projects/worldchess-font/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Шрифт для цен</u> Ворлд Чесс
          </div>
        </a>
      </div>

      <div class="project sizeM">
        <a href="/projects/worldchess-2/">
          <div class="project-media">
            <img src="/projects/worldchess-2/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Вторая версия главной страницы</u> Ворлд Чесса
          </div>
        </a>
      </div>

      <div class="project sizeS">
        <a href="/projects/worldchess-broadcasts/">
          <div class="project-media">
            <img src="/projects/worldchess-broadcasts/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Ворлд Чесс</u> ведёт трансляции шахматных турниров
          </div>
        </a>
      </div>

      <div class="project sizeM">
        <a href="/projects/vashrepetitor-logo/">
          <div class="project-media">
            <img src="/projects/vashrepetitor-logo/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Логотип</u> «Вашего репетитора» — сервиса подбора репетиторов
          </div>
        </a>
      </div>

      <div class="project sizeM">
        <a href="/projects/mailganer/">
          <div class="project-media">
            <img src="/projects/mailganer/files/cover.jpg">
          </div>
          <div class="project-caption">
            <u>Сайт Мейлганера</u> — кросс-канальной платформы маркетинга
          </div>
        </a>
      </div>
    </section>
  </div>
  
  <script>
    const MOBILE_LAYOUT = ['L', 'S', 'S']

    document
      .querySelectorAll('.project')
      .forEach((project, index) => {
        const layoutClass = MOBILE_LAYOUT[index % MOBILE_LAYOUT.length]
        const mobileClass = `mobileSize${layoutClass}`

        project
          .classList
          .add(mobileClass)
      })
  </script>
</body>
</html>
:root {
  --textColor: #000;
  --linkColor: #00aff0;
  --keyColor: #ff5e00;
}

body {
  font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
  color: var(--textColor);
}

a,
a u {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .175em;
  text-decoration-color: color-mix(in srgb, currentColor, transparent 80%);
  text-decoration-skip: none;
  text-decoration-skip-ink: none;
  color: var(--linkColor);
}

a:has(u) {
  text-decoration: none;
}

a:hover,
a:hover u {
  color: var(--keyColor);
  text-decoration-color: currentColor;
}

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  column-gap: 20px;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  border-radius: 4px;
  background: #f3f5f4;
  min-height: 33vh;
  overflow: hidden;
}

.project a,
.project a:hover {
  color: var(--textColor);
}

.project-media {
  margin: -10px -10px 0;
  aspect-ratio: 1.91 / 1;
}

.project-caption {
  margin-top: .5em;
  font-size: 12px;
  line-height: 1.2;
}

.project.sizeS {
  grid-column: auto / span 1;
}

.project.sizeM {
  grid-column: auto / span 2;
}

.project.sizeL {
  grid-column: auto / span 4;
}

@media (width <= 768px) {
  .projects {
    grid-template-columns: 1fr 1fr;
    grid-auto-flow: dense;
  }

  .project.mobileSizeS,
  .project.mobileSizeM {
    grid-column: auto / span 1;
  }

  .project.sizeL,
  .project.mobileSizeL {
    grid-column: auto / span 2;
  }
}

Рас­кладка как в Пин­те­ресе — это част­ный слу­чай плитки, в кото­рой высота ячеек пере­мен­ная. Кар­точки впи­сы­ва­ются в колонки, а по рядам рас­по­ла­га­ются так, чтобы при­жаться вплот­ную друг к другу.

К сожа­ле­нию, в ЦСС пока нет хоро­шей под­держки этой рас­кладки. В свет­лом буду­щем она могла бы вклю­чаться с помо­щью grid-template-rows: masonry. Но пока это под­дер­жи­ва­ется только в послед­ней вер­сии Сафари и Файр­фокса, если вклю­чить в настройках.

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  grid-template-rows: masonry;
  column-gap: 20px;
  row-gap: 20px;
}

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

<section class="projects">
  <!-- Добавим колонки, по которым будем расставлять проекты: -->
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
  
  <div class="project col-1"></div> <!-- Этот проект пойдёт в первую колонку -->
  <div class="project col-2"></div> <!-- Этот проект пойдёт во вторую колонку -->
  <div class="project col-3"></div> <!-- В третью колонку -->
  <div class="project col-4"></div> <!-- В четвёртую колонку -->
</section>

<script>
  const columns = document
    .querySelectorAll('.projects > .col') // Находим колонки

  columns.forEach((column, index) => {
    const projects = document
      .querySelectorAll(`.project.col-${index + 1}`)

    column.append(...projects) // Перемещаем в них проекты с подходящим классом
  })
</script>
.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  column-gap: 20px;
}

.projects > .col { /* Теперь грид-контейнер содержит только четыре колонки, а проекты вложены в них */
  display: grid;
  grid-auto-rows: max-content;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  background: #f3f5f4;
  border-radius: 4px;
  overflow: hidden;
}

.project a {
  color: var(--textColor);
}

.project-media {
  margin: -10px -10px 0;
}

.project-caption {
  margin-top: .5em;
  font-size: 14px;
  line-height: 1.25;
}

@media (width <= 768px) {
  .projects { /* На мобильных перестраиваем колонки по две в ряд */
    grid-template-columns: 1fr 1fr;
  }
}

И, конечно, есть тре­тий вари­ант сде­лать такую рас­кладку — с помо­щью про­грам­ми­ро­ва­ния и гото­вых библиотек:

Раскладка как в Пинтересе — это частный случай плитки, в которой высота ячеек переменная. Карточки вписываются в колонки, а по рядам располагаются так, чтобы прижаться вплотную друг к другу.

К сожалению, в ЦСС пока нет хорошей поддержки этой раскладки. В светлом будущем она могла бы включаться с помощью grid-template-rows: masonry. Но пока это поддерживается только в последней версии Сафари и Файрфокса, если включить в настройках.

.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  grid-auto-rows: max-content;
  grid-template-rows: masonry;
  column-gap: 20px;
  row-gap: 20px;
}

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

<section class="projects">
  <!-- Добавим колонки, по которым будем расставлять проекты: -->
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
  <div class="col"></div>
  
  <div class="project col-1"></div> <!-- Этот проект пойдёт в первую колонку -->
  <div class="project col-2"></div> <!-- Этот проект пойдёт во вторую колонку -->
  <div class="project col-3"></div> <!-- В третью колонку -->
  <div class="project col-4"></div> <!-- В четвёртую колонку -->
</section>

<script>
  const columns = document
    .querySelectorAll('.projects > .col') // Находим колонки

  columns.forEach((column, index) => {
    const projects = document
      .querySelectorAll(`.project.col-${index + 1}`)

    column.append(...projects) // Перемещаем в них проекты с подходящим классом
  })
</script>
.projects {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  column-gap: 20px;
}

.projects > .col { /* Теперь грид-контейнер содержит только четыре колонки, а проекты вложены в них */
  display: grid;
  grid-auto-rows: max-content;
  row-gap: 20px;
}

.project {
  padding: 10px 10px 20px;
  background: #f3f5f4;
  border-radius: 4px;
  overflow: hidden;
}

.project a {
  color: var(--textColor);
}

.project-media {
  margin: -10px -10px 0;
}

.project-caption {
  margin-top: .5em;
  font-size: 14px;
  line-height: 1.25;
}

@media (width <= 768px) {
  .projects { /* На мобильных перестраиваем колонки по две в ряд */
    grid-template-columns: 1fr 1fr;
  }
}

И, конечно, есть третий вариант сделать такую раскладку — с помощью программирования и готовых библиотек:

Промостраница

Задача про­мо­стра­ницы — про­дать товар или услугу, собрать кон­такт­ную инфор­ма­цию посе­ти­те­лей. Напол­не­ние зави­сит от того, что и кому про­даём. Обычно это раз­ные ком­би­на­ции кар­ти­нок про­дукта и тек­ста о его преимуществах.

На про­мах обычно раз­ме­щают при­зыв к дей­ствию (call to action) — модуль с кноп­кой покупки, фор­мой реги­стра­ции или теле­фо­ном для связи.

Раз­бе­рём вёрстку про­мо­стра­ницы на при­мере бюро.

Промостраница

Задача промостраницы — продать товар или услугу, собрать контактную информацию посетителей. Наполнение зависит от того, что и кому продаём. Обычно это разные комбинации картинок продукта и текста о его преимуществах.

На промах обычно размещают призыв к действию (call to action) — модуль с кнопкой покупки, формой регистрации или телефоном для связи.

Разберём вёрстку промостраницы на примере бюро.

Выра­зи­тель­ную шапку стра­ницы в запад­ном веб‑дизайне назы­вают «героем» (hero). Часто в её вёрстке исполь­зуют aspect-ratio и абсо­лют­ное пози­ци­о­ни­ро­ва­ние, чтобы рас­ста­вить модули вокруг и поверх текста.

Здесь и далее в коде сосре­до­то­чимся на рас­кладке, не вда­ва­ясь в подроб­но­сти сти­ли­за­ции внут­рен­них моду­лей. Об этом доста­точно ска­зано в про­шлых главах.

<!-- Контейнер на всю ширину -->
<div class="hero">

  <!-- Контентная область с ограниченной шириной -->
  <div class="heroIn">
    <div class="heroInfo">
      <b>Дистанционный курс …</b>
      <p>Перерождение классического курса …</p>
      <p>
        <span class="serif">24 февраля — 22 марта.</span>
        <b class="heroExpire">Запись открыта до ВС 23 фев</b>
      </p>
    </div>

    <h1 class="heroTitle">
      <!-- Здесь br уместен -->
      Переговоры <br/>
      и отношения <br/>
      с клиентами

      <span class="heroTitleVersion">2.0</span>
    </h1>

    <img class="heroAuthor" src="./ilya.png" alt="Фото Ильи Синельникова">

    <img class="heroGorilla" src="./gorilla.png" alt="…">
    <img class="heroChuvak" src="./chuvak.png" alt="…">
  </div>
</div>
.hero {
  background: #FCBF32;
  color: #000;
  padding-top: 60px;
}

.heroIn {
  max-width: 768px;
  margin: 0 auto;
  aspect-ratio: 16/9;
  /* Контекст для позиционирования «абсолютных» модулей */
  position: relative;
}

.heroInfo {
  margin-bottom: 10px;
}

.heroExpire {
  color: #fc5621;
}

.heroTitle {
  font-size: 6vw;
  text-transform: uppercase;
}

.heroTitleVersion {
  display: block;
  text-align: right;
  color: #fc5621;
}

.heroAuthor {
  position: absolute;
  bottom: 0;
  /* Трюк для центровки модуля: сдвиг 50% родителя + смещение -50% собственной ширины*/
  left: 50%;
  translate: -50% 0;

  max-width: 50%;
}

.heroGorilla {
  position: absolute;
  top: 5%;
  right: 3%;
  max-width: 25%;
  transform: rotate(-5deg);
}

.heroChuvak {
  position: absolute;
  bottom: 25%;
  left: 7%;
  max-width: 15%;
  transform: rotate(-5deg);
}

Выразительную шапку страницы в западном веб‑дизайне называют «героем» (hero). Часто в её вёрстке используют aspect-ratio и абсолютное позиционирование, чтобы расставить модули вокруг и поверх текста.

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

<!-- Контейнер на всю ширину -->
<div class="hero">

  <!-- Контентная область с ограниченной шириной -->
  <div class="heroIn">
    <div class="heroInfo">
      <b>Дистанционный курс …</b>
      <p>Перерождение классического курса …</p>
      <p>
        <span class="serif">24 февраля — 22 марта.</span>
        <b class="heroExpire">Запись открыта до ВС 23 фев</b>
      </p>
    </div>

    <h1 class="heroTitle">
      <!-- Здесь br уместен -->
      Переговоры <br/>
      и отношения <br/>
      с клиентами

      <span class="heroTitleVersion">2.0</span>
    </h1>

    <img class="heroAuthor" src="./ilya.png" alt="Фото Ильи Синельникова">

    <img class="heroGorilla" src="./gorilla.png" alt="…">
    <img class="heroChuvak" src="./chuvak.png" alt="…">
  </div>
</div>
.hero {
  background: #FCBF32;
  color: #000;
  padding-top: 60px;
}

.heroIn {
  max-width: 768px;
  margin: 0 auto;
  aspect-ratio: 16/9;
  /* Контекст для позиционирования «абсолютных» модулей */
  position: relative;
}

.heroInfo {
  margin-bottom: 10px;
}

.heroExpire {
  color: #fc5621;
}

.heroTitle {
  font-size: 6vw;
  text-transform: uppercase;
}

.heroTitleVersion {
  display: block;
  text-align: right;
  color: #fc5621;
}

.heroAuthor {
  position: absolute;
  bottom: 0;
  /* Трюк для центровки модуля: сдвиг 50% родителя + смещение -50% собственной ширины*/
  left: 50%;
  translate: -50% 0;

  max-width: 50%;
}

.heroGorilla {
  position: absolute;
  top: 5%;
  right: 3%;
  max-width: 25%;
  transform: rotate(-5deg);
}

.heroChuvak {
  position: absolute;
  bottom: 25%;
  left: 7%;
  max-width: 15%;
  transform: rotate(-5deg);
}

Ино­гда нет вре­мени или воз­мож­но­сти вер­стать слож­ную типо­гра­фику заголовка.

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

Трюк: раз­ме­стите поверх кар­тинки визу­ально скры­тый «чест­ный» заго­ло­вок. Поставьте текст как можно точ­нее по кар­тинке, чтобы выде­ле­ние тек­ста выгля­дело адекватно.

Скройте текст, сде­лав его прозрачным.

Опасность. Исполь­зуйте трюк исклю­чи­тельно для дуб­ли­ро­ва­ния тек­ста, содер­жа­ще­гося на кар­тинке. Наме­рен­ное скры­тие тек­ста от поль­зо­ва­те­лей назы­ва­ется гостин­гом (ghosting) и сурово нака­зы­ва­ется поис­ко­ви­ками, вплоть до исклю­че­ния сайта из поис­ко­вой выдачи.

<div class="fakeHeading">
  <!-- По возможности используйте СВГ -->
  <img class="fakeHeadingImg" src="./fake-heading.svg" alt="Красивый заголовок: переговоры и отношения с клиентами">

  <h1 class="fakeHeadingHidden">
    Переговоры <br>
    и отношения <br>
    с клиентами
  </h1>
</div>
.fakeHeading {
  max-width: 100%;
  position: relative;
  overflow: hidden; /* На всякий случай, чтобы текст настоящего заголовка не протёк за пределы модуля */
}

.fakeHeadingImg {
  display: block;
  width: 100%;
}

.fakeHeadingHidden {
  position: absolute;
  top: 0;
  left: 0;
  font-size: 5vw;
  color: transparent;

  /* Подгоняем текст по к картинке */
  margin: 0;
  text-transform: uppercase;
  font-size: 12.2vw;
  line-height: 1.05;
}
Пере­го­воры
и отно­ше­ния
с кли­ен­тами
Пере­го­воры
и отно­ше­ния
с кли­ен­тами
Переговоры
и отношения
с клиентами
Переговоры
и отношения
с клиентами

Иногда нет времени или возможности верстать сложную типографику заголовка.

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

Трюк: разместите поверх картинки визуально скрытый «честный» заголовок. Поставьте текст как можно точнее по картинке, чтобы выделение текста выглядело адекватно.

Скройте текст, сделав его прозрачным.

Опасность. Используйте трюк исключительно для дублирования текста, содержащегося на картинке. Намеренное скрытие текста от пользователей называется гостингом (ghosting) и сурово наказывается поисковиками, вплоть до исключения сайта из поисковой выдачи.

<div class="fakeHeading">
  <!-- По возможности используйте СВГ -->
  <img class="fakeHeadingImg" src="./fake-heading.svg" alt="Красивый заголовок: переговоры и отношения с клиентами">

  <h1 class="fakeHeadingHidden">
    Переговоры <br>
    и отношения <br>
    с клиентами
  </h1>
</div>
.fakeHeading {
  max-width: 100%;
  position: relative;
  overflow: hidden; /* На всякий случай, чтобы текст настоящего заголовка не протёк за пределы модуля */
}

.fakeHeadingImg {
  display: block;
  width: 100%;
}

.fakeHeadingHidden {
  position: absolute;
  top: 0;
  left: 0;
  font-size: 5vw;
  color: transparent;

  /* Подгоняем текст по к картинке */
  margin: 0;
  text-transform: uppercase;
  font-size: 12.2vw;
  line-height: 1.05;
}

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

Для уско­ре­ния работы удобно заве­сти уни­вер­саль­ные слу­жеб­ные классы — как для рас­кладки, так и для повто­ря­ю­щихся модулей.

<div class="rows">
  <div class="row">
    <div class="cols">
      <div class="col4">…</div>
      <div class="col6">…</div>
      <div class="col6">…</div>
    </div>
  </div>

  <div class="row">
    <h2>Добиваться своего …</h2>
    <div class="cols">
      <div class="col8">…</div>
      <div class="col4">…</div>
      <div class="col4">…</div>
    </div>
  </div>

  <div class="row">
    <h2>Больше зарабатывать</h2>
    <div class="cols">
      <div class="col4">…</div>
      <div class="col4">…</div>
      <div class="col8">…</div>
    </div>
  </div>
</div>
.rows {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.cols {
  display: flex;
  gap: 20px;
}

.col2 {
  flex-grow: 2;
}

.col6 {
  flex-grow: 6;
}

.col8 {
  flex-grow: 8;
}

/* … */

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

Для ускорения работы удобно завести универсальные служебные классы — как для раскладки, так и для повторяющихся модулей.

<div class="rows">
  <div class="row">
    <div class="cols">
      <div class="col4">…</div>
      <div class="col6">…</div>
      <div class="col6">…</div>
    </div>
  </div>

  <div class="row">
    <h2>Добиваться своего …</h2>
    <div class="cols">
      <div class="col8">…</div>
      <div class="col4">…</div>
      <div class="col4">…</div>
    </div>
  </div>

  <div class="row">
    <h2>Больше зарабатывать</h2>
    <div class="cols">
      <div class="col4">…</div>
      <div class="col4">…</div>
      <div class="col8">…</div>
    </div>
  </div>
</div>
.rows {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.cols {
  display: flex;
  gap: 20px;
}

.col2 {
  flex-grow: 2;
}

.col6 {
  flex-grow: 6;
}

.col8 {
  flex-grow: 8;
}

/* … */

Отзывы. Важ­ный модуль на про­мо­стра­нице — отзывы. Для выра­зи­тель­но­сти добавьте фото­гра­фии авто­ров, выносы важ­ных мыс­лей. Здесь так же полезно чере­до­вать ритм по вер­ти­кали: вер­стайте внут­рен­ние модули так, чтобы их можно было менять местами и ничего не ломалось.

<div class="reviews">
  <h2>Отзывы</h2>

  <div class="review">
    <div class="reviewAuthor">…</div>

    <div class="reviewContent">…</div>

    <div class="reviewSide">
      <div class="liftOut">…</div>
    </div>
  </div>

  <div class="review">
    <div class="reviewSide">
      <div class="liftOut">…</div>
    </div>

    <div class="reviewContent">…</div>

    <div class="reviewAuthor">…</div>
  </div>
</div>
.review {
  display: flex;
  gap: 10px;
}

.review + .review {
  margin-top: 20px;
}

.reviewAuthor {
  flex-grow: 2;
}

.reviewContent {
  flex-grow: 9;
}

.reviewSide {
  flex-grow: 2;
}

Отзывы. Важный модуль на промостранице — отзывы. Для выразительности добавьте фотографии авторов, выносы важных мыслей. Здесь так же полезно чередовать ритм по вертикали: верстайте внутренние модули так, чтобы их можно было менять местами и ничего не ломалось.

<div class="reviews">
  <h2>Отзывы</h2>

  <div class="review">
    <div class="reviewAuthor">…</div>

    <div class="reviewContent">…</div>

    <div class="reviewSide">
      <div class="liftOut">…</div>
    </div>
  </div>

  <div class="review">
    <div class="reviewSide">
      <div class="liftOut">…</div>
    </div>

    <div class="reviewContent">…</div>

    <div class="reviewAuthor">…</div>
  </div>
</div>
.review {
  display: flex;
  gap: 10px;
}

.review + .review {
  margin-top: 20px;
}

.reviewAuthor {
  flex-grow: 2;
}

.reviewContent {
  flex-grow: 9;
}

.reviewSide {
  flex-grow: 2;
}

Призыв к действию. Клю­че­вой модуль про­мо­стра­ницы — при­зыв к целе­вому дей­ствию (CTA, call to action). Мы назы­ваем этот блок товар­ным пред­ло­же­нием. Рас­кладка и состав зави­сят от продукта.

Рецепт вёрстки клас­си­че­ский: собе­рите рас­кладку флек­сами или гри­дами, затем после­до­ва­тельно сти­ли­зуйте внут­рен­ние элементы.

<div class="cols">
  <div class="col">
    <h3>Когда что выйдет</h3>
    <p>…</p>
    <form class="subscriptionForm">…</form>
  </div>
  <div class="col">
    <h3>Подписка</h3>
    <div class="prices">…</div>
    <p>…</p>
  </div>
</div>

Кнопки призыва к действию можно разместить в самом модуле, но обычно их закрепляют и держат на виду во время прокрутки страницы. Как это сделать — разберём дальше

Призыв к действию. Ключевой модуль промостраницы — призыв к целевому действию (CTA, call to action). Мы называем этот блок товарным предложением. Раскладка и состав зависят от продукта.

Рецепт вёрстки классический: соберите раскладку флексами или гридами, затем последовательно стилизуйте внутренние элементы.

Кнопки призыва к действию можно разместить в самом модуле, но обычно их закрепляют и держат на виду во время прокрутки страницы. Как это сделать — разберём дальше

<div class="cols">
  <div class="col">
    <h3>Когда что выйдет</h3>
    <p>…</p>
    <form class="subscriptionForm">…</form>
  </div>
  <div class="col">
    <h3>Подписка</h3>
    <div class="prices">…</div>
    <p>…</p>
  </div>
</div>

Залипающая кнопка. Чтобы кнопка оста­ва­лась на виду при про­крутке, её можно зафик­си­ро­вать с помо­щью position: fixed. Но чтобы огра­ни­чить диа­па­зон про­крутки и кнопка не зале­зала в шапку и под­вал, лучше исполь­зо­вать position: sticky в «шахте».

<div class="promo">
  <!-- … -->
  <div class="stickyShaft">
    <div class="stickyContent">…</div>
  </div>
</div>
.stickyShaft {
  position: absolute;
  right: 32px;
  bottom: 144px;
  top: 75vh;
  width: 0;
}

.stickyContent {
  position: sticky;
  top: calc(100vh - 75px);
}

Залипающая кнопка. Чтобы кнопка оставалась на виду при прокрутке, её можно зафиксировать с помощью position: fixed. Но чтобы ограничить диапазон прокрутки и кнопка не залезала в шапку и подвал, лучше использовать position: sticky в «шахте».

<div class="promo">
  <!-- … -->
  <div class="stickyShaft">
    <div class="stickyContent">…</div>
  </div>
</div>
.stickyShaft {
  position: absolute;
  right: 32px;
  bottom: 144px;
  top: 75vh;
  width: 0;
}

.stickyContent {
  position: sticky;
  top: calc(100vh - 75px);
}

Интерактивная статья

Совре­мен­ная интер­ак­тив­ная ста­тья содер­жит фото­гра­фии, инфо­гра­фику, видео и аудио, ани­ма­цию, пере­бивки и сноски раз­ного фор­мата — для кра­соты, инте­рес­ной и понят­ной подачи материала.

Раз­бе­рём при­ёмы вёрстки интер­ак­тив­ной статьи.

Вёрстка базовой текстовой страницы:

Захари Смол и Рамси Тейлор. How The Legend of Zelda Changed the Game

Интерактивная статья

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

Разберём приёмы вёрстки интерактивной статьи.

Вёрстка базовой текстовой страницы:

Захари Смол и Рамси Тейлор. How The Legend of Zelda Changed the Game

Бутерброд

Самая рас­про­стра­нён­ная струк­тура интер­ак­тив­ной ста­тьи — «бутер­брод», в кото­ром модули рас­по­ло­жены сло­ями, или эта­жами, друг под другом.

Обычно для ста­тьи доста­точно двух видов слоёв: с огра­ни­чен­ной шири­ной для тек­ста и на всю ширину для обложки, гра­фики и дру­гих пере­би­вок. Слои — уни­вер­саль­ные кон­тей­неры для чего угодно: кар­ти­нок, тек­ста и даже дру­гих слоёв.

.layer {
  max-width: 768px;
  margin: 0 auto;
}

.layer.fullWidth {
  max-width: none;
}

.layer + .layer {
  margin-top: 72px;
}
<div class="layer fullWidth">
  <img src="cover.jpg" alt="…">
</div>

<div class="layer">
  <p>…</p>
  <p>…</p>
</div>

<div class="layer">
  <figure class="image">
    <img src="…" alt="…">
    <figcaption>…</figcaption>
  </figure>
</div>

<div class="layer">
  <p>…</p>
</div>

Бутерброд

Самая распространённая структура интерактивной статьи — «бутерброд», в котором модули расположены слоями, или этажами, друг под другом.

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

.layer {
  max-width: 768px;
  margin: 0 auto;
}

.layer.fullWidth {
  max-width: none;
}

.layer + .layer {
  margin-top: 72px;
}
<div class="layer fullWidth">
  <img src="cover.jpg" alt="…">
</div>

<div class="layer">
  <p>…</p>
  <p>…</p>
</div>

<div class="layer">
  <figure class="image">
    <img src="…" alt="…">
    <figcaption>…</figcaption>
  </figure>
</div>

<div class="layer">
  <p>…</p>
</div>

Фоторама

Фото­рама — это гале­рея фото­гра­фий с одной круп­ной фото­гра­фией и воз­мож­но­стью про­ли­сты­ва­ния и выбора дру­гих фото­гра­фий по превьюшкам.

В ХТМЛ нет спе­ци­аль­ных тегов для вёрстки фото­рамы, для этого исполь­зуют Яваскрипт‑библиотеки.

Сове­туем биб­лио­теку «Фото­рама» Артёма Поли­кар­пова. Она под­клю­ча­ется в одно дви­же­ние, акку­ратно выгля­дит, умеет откры­вать фото на весь экран и под­дер­жи­вает видео.

Ска­чайте архив с сайта «Фото­рамы», рас­па­куйте файлы рядом с вашей вёрст­кой и добавьте код в head:

<!-- Вспомогательные библиотеки, подключаем из интернета, но можно скачать и тоже держать рядом с вёрсткой -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<link href="./fotorama.css" rel="stylesheet">
<script src="./fotorama.js">
</script>

Затем в нуж­ном месте стра­ницы обер­ните кар­тинки в эле­мент с клас­сом fotorama. Атри­бу­тами по жела­нию вклю­чите инфор­ма­тив­ные пре­вьюшки и воз­мож­ность раз­во­ра­чи­вать фото на весь экран. Инструк­ции на сайте помо­гут настро­ить дизайн и пове­де­ние «Фоторамы».

<div class="fotorama"
  data-allowfullscreen="true"
  data-nav="thumbs"
  data-thumbwidth="160px"
  data-thumbheight="90px">

  <img src="./house-1.jpg">
  <img src="./house-2.jpg">
  <img src="./house-3.jpg">
  <img src="./house-4.jpg">
  <img src="./house-5.jpg">
</div>

Фоторама

Фоторама — это галерея фотографий с одной крупной фотографией и возможностью пролистывания и выбора других фотографий по превьюшкам.

В ХТМЛ нет специальных тегов для вёрстки фоторамы, для этого используют Яваскрипт‑библиотеки.

Советуем библиотеку «Фоторама» Артёма Поликарпова. Она подключается в одно движение, аккуратно выглядит, умеет открывать фото на весь экран и поддерживает видео.

Скачайте архив с сайта «Фоторамы», распакуйте файлы рядом с вашей вёрсткой и добавьте код в head:

<!-- Вспомогательные библиотеки, подключаем из интернета, но можно скачать и тоже держать рядом с вёрсткой -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

<link href="./fotorama.css" rel="stylesheet">
<script src="./fotorama.js">
</script>

Затем в нужном месте страницы оберните картинки в элемент с классом fotorama. Атрибутами по желанию включите информативные превьюшки и возможность разворачивать фото на весь экран. Инструкции на сайте помогут настроить дизайн и поведение «Фоторамы».

<div class="fotorama"
  data-allowfullscreen="true"
  data-nav="thumbs"
  data-thumbwidth="160px"
  data-thumbheight="90px">

  <img src="./house-1.jpg">
  <img src="./house-2.jpg">
  <img src="./house-3.jpg">
  <img src="./house-4.jpg">
  <img src="./house-5.jpg">
</div>

Плееры видеохостингов умеют запоминать позицию, настраивать субтитры и скорость просмотра

Видео

Самый про­стой спо­соб доба­вить видео на стра­ницу — загру­зить на видео­хо­стинг (Ютуб, ВК, Вимео) и встро­ить оттуда.

Так вы не замо­ра­чи­ва­е­тесь с хра­не­нием видео, полу­ча­ете про­дви­ну­тый плеер, а глав­ное — пото­ко­вое вос­про­из­ве­де­ние: видео можно начать смот­реть с любого места, не дожи­да­ясь пол­ной загрузки. Это осо­бенно полезно для длин­ных лек­ций или новостей.

Минус видео­хо­стин­гов — видео хра­нится не у вас. Хостинг может вдруг закрыться или уда­лить ваши видео, хра­ните резерв­ные копии на чёр­ный день.

Прин­цип встра­и­ва­ния похож у раз­ных хостин­гов: нажмите «Поде­литься» на стра­нице видео и най­дите код встра­и­ва­ния, обычно это тег iframe. Тег iframe встра­и­вает на стра­ницу кусо­чек дру­гого сайта, в нашем слу­чае — видеоплеер:

<!-- Уберите из кода встраивания атрибуты width и height, если хотите задать размеры в ЦСС -->
<iframe class="vkVideo" src="https://vk.com/video_ext.php?oid=-113157690&id=456239826&hd=2" width="853" height="480" allow="autoplay; encrypted-media; fullscreen; picture-in-picture; screen-wake-lock;" frameborder="0" allowfullscreen></iframe>
.vkVideo {
  aspect-ratio: 16/9;
}

Плееры видеохостингов умеют запоминать позицию, настраивать субтитры и скорость просмотра

Видео

Самый простой способ добавить видео на страницу — загрузить на видеохостинг (Ютуб, ВК, Вимео) и встроить оттуда.

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

Минус видеохостингов — видео хранится не у вас. Хостинг может вдруг закрыться или удалить ваши видео, храните резервные копии на чёрный день.

Принцип встраивания похож у разных хостингов: нажмите «Поделиться» на странице видео и найдите код встраивания, обычно это тег iframe. Тег iframe встраивает на страницу кусочек другого сайта, в нашем случае — видеоплеер:

<!-- Уберите из кода встраивания атрибуты width и height, если хотите задать размеры в ЦСС -->
<iframe class="vkVideo" src="https://vk.com/video_ext.php?oid=-113157690&id=456239826&hd=2" width="853" height="480" allow="autoplay; encrypted-media; fullscreen; picture-in-picture; screen-wake-lock;" frameborder="0" allowfullscreen></iframe>
.vkVideo {
  aspect-ratio: 16/9;
}

Видео­файл можно раз­ме­стить в фай­лах вашего сайта и под­клю­чать вруч­ную тегом video:

<!-- poster — путь до обложки, которая будет видна, пока видео не включат, controls — включает элементы управления плеером -->
<video 
  class="video" 
  src="./videos/rickroll.mp4" 
  poster="./videos/rickroll-preview.jpg"
  controls
></video>
.video {
  width: 100%;
  aspect-ratio: 4/3.15;
  /* Заклинание для масштабирования видео под любые размеры тега */
  object-fit: cover;
  object-position: center;
}

С руч­ным под­клю­че­нием много заморочек.

Хотя базо­вый при­мер кода выгля­дит про­сто, он плохо подой­дёт для сай­тов с боль­шой посе­ща­е­мо­стью. По‑хоро­шему, видео­файлы надо осо­бым обра­зом опти­ми­зи­ро­вать и гото­вить в несколь­ких фор­ма­тах, писать много допол­ни­тель­ного кода, чтобы видео не тор­мо­зило и ста­бильно рабо­тало на раз­ных устрой­ствах и браузерах.

При про­иг­ры­ва­нии из файла не обес­пе­чить пото­ко­вое вос­про­из­ве­де­ние без про­грам­ми­ро­ва­ния или под­клю­че­ния сер­ви­сов стри­минга — не полу­чится быстро начать про­смотр с любого места. В стан­дарт­ном пле­ере мини­мум функ­ций. В таком «сыром» виде, это не слиш­ком удобно зрителю.

Ещё видео­файлы при­дётся хра­нить в фай­лах сайта на сер­вере. Место на сер­вере плат­ное, хра­не­ние тяжё­лых фай­лов вле­тит в копеечку.

Руч­ное под­клю­че­ние подой­дёт для кро­шеч­ных видео, вроде неслож­ного ани­ми­ро­ван­ного фона. Или когда кри­ти­че­ски важно хра­нить видео у себя и все­гда иметь к нему доступ, и вы готовы ради этого замо­ро­читься или закрыть глаза на пере­чис­лен­ные недостатки.

Видеофайл можно разместить в файлах вашего сайта и подключать вручную тегом video:

<!-- poster — путь до обложки, которая будет видна, пока видео не включат, controls — включает элементы управления плеером -->
<video 
  class="video" 
  src="./videos/rickroll.mp4" 
  poster="./videos/rickroll-preview.jpg"
  controls
></video>
.video {
  width: 100%;
  aspect-ratio: 4/3.15;
  /* Заклинание для масштабирования видео под любые размеры тега */
  object-fit: cover;
  object-position: center;
}

С ручным подключением много заморочек.

Хотя базовый пример кода выглядит просто, он плохо подойдёт для сайтов с большой посещаемостью. По‑хорошему, видеофайлы надо особым образом оптимизировать и готовить в нескольких форматах, писать много дополнительного кода, чтобы видео не тормозило и стабильно работало на разных устройствах и браузерах.

При проигрывании из файла не обеспечить потоковое воспроизведение без программирования или подключения сервисов стриминга — не получится быстро начать просмотр с любого места. В стандартном плеере минимум функций. В таком «сыром» виде, это не слишком удобно зрителю.

Ещё видеофайлы придётся хранить в файлах сайта на сервере. Место на сервере платное, хранение тяжёлых файлов влетит в копеечку.

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

Неболь­шие зацик­лен­ные видео часто исполь­зуют как деко­ра­цию или инте­рес­ное превью.

Вклю­чите авто­вос­про­из­ве­де­ние атри­бу­том autoplay. Он рабо­тает только в связке с muted, уби­ра­ю­щим звук — так бра­у­зеры защи­щают чита­те­лей от назой­ли­вых видео. Атри­бут loop зацик­лит видео. Затем пози­ци­о­ни­рйте, в зави­си­мо­сти от задачи.

Настро­ить подоб­ное пове­де­ние можно и для айфрейма видео­хо­стинга, акту­аль­ные инструк­ции по атри­бу­там лучше поис­кать в интер­нете, они ино­гда меняются.

<div class="hero">
  <!-- Видео должно весить до пары мегабайт, чтобы не тормозить загрузку страницы -->
  <video class="hero__bg" src="./nggyu.mp4" autoplay muted loop></video>

  <div class="heroText">
    <h1>Рикрол</h1>
    <p>…</p>
  </div>
</div>
.hero {
  position: relative;
  aspect-ratio: 16/8;
}

.heroBg {
  position: absolute;
  z-index: 0;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;

  /* Видео лучше отключить кликабельность */
  pointer-events: none;
}

.heroText {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 10%;
  translate: -50% 0;
}

Рикролл

От анонимных форумов до мировой известности. Полная история легендарного мема в лонгриде Арины Черновой

В интер­нет‑куль­туре мно­же­ство мемов, но лишь немно­гие из них смогли выйти за пре­делы экра­нов и стать частью повсе­днев­ной жизни. Один из таких фено­ме­нов — рикрол­линг, розыг­рыш, кото­рый начался на ано­ним­ном ими­дж­борде 4chan и пре­вра­тился в гло­баль­ное явле­ние. Рикрол­линг стал одним из пер­вых мемов, достиг­ших все­мир­ной популярности.

Рикролл

От анонимных форумов до мировой известности. Полная история легендарного мема в лонгриде Арины Черновой

В интернет‑культуре множество мемов, но лишь немногие из них смогли выйти за пределы экранов и стать частью повседневной жизни. Один из таких феноменов — рикроллинг, розыгрыш, который начался на анонимном имиджборде 4chan и превратился в глобальное явление. Рикроллинг стал одним из первых мемов, достигших всемирной популярности.

Небольшие зацикленные видео часто используют как декорацию или интересное превью.

Включите автовоспроизведение атрибутом autoplay. Он работает только в связке с muted, убирающим звук — так браузеры защищают читателей от назойливых видео. Атрибут loop зациклит видео. Затем позиционирйте, в зависимости от задачи.

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

<div class="hero">
  <!-- Видео должно весить до пары мегабайт, чтобы не тормозить загрузку страницы -->
  <video class="hero__bg" src="./nggyu.mp4" autoplay muted loop></video>

  <div class="heroText">
    <h1>Рикрол</h1>
    <p>…</p>
  </div>
</div>
.hero {
  position: relative;
  aspect-ratio: 16/8;
}

.heroBg {
  position: absolute;
  z-index: 0;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;

  /* Видео лучше отключить кликабельность */
  pointer-events: none;
}

.heroText {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 10%;
  translate: -50% 0;
}

Пример стилизации видеоплеера с помощью media‑chrome

Если хра­ните видео у себя и вас не устра­и­вает вид стан­дарт­ного пле­ера тега video, можно под­клю­чить биб­лио­теку для настройки сво­его пле­ера. Напри­мер, VideoJS или media‑chrome.

На 2025 год биб­лио­теки не дают каких‑то осо­бых функ­ций вроде стри­минга. Зато можно сти­ли­зо­вать плеер, чтобы он выгля­дел оди­на­ково акку­ратно в раз­ных браузерах.

Инструк­ции по под­клю­че­нию и настройке ищите на сайте кон­крет­ной библиотеки.

Пример стилизации видеоплеера с помощью media‑chrome

Если храните видео у себя и вас не устраивает вид стандартного плеера тега video, можно подключить библиотеку для настройки своего плеера. Например, VideoJS или media‑chrome.

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

Инструкции по подключению и настройке ищите на сайте конкретной библиотеки.

Если хра­ните видео у себя, исполь­зуйте фор­мат МП4 и обя­за­тельно опти­ми­зи­руйте видео­файл, чтобы умень­шить его раз­мер и уско­рить загрузку. Про­стой спо­соб опти­ми­за­ции — сайт МП4 Компрес.

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

А если видео не под­ра­зу­ме­вает обя­за­тель­ного про­смотра каж­дым чита­те­лем, можно отклю­чить его пред­за­грузку атри­бу­том preload="none". По умол­ча­нию бра­у­зер пред­за­гру­жает начало видео­файла, зна­че­ние none отклю­чает это пове­де­ние и чуть уско­ряет загрузку стра­ницы. Но тогда уже при вклю­че­нии видео чита­телю при­дётся подо­ждать чуть дольше.

<video controls preload="none" src="./about-us.mp4">
</video>

Если храните видео у себя, используйте формат МП4 и обязательно оптимизируйте видеофайл, чтобы уменьшить его размер и ускорить загрузку. Простой способ оптимизации — сайт МП4 Компрес.

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

А если видео не подразумевает обязательного просмотра каждым читателем, можно отключить его предзагрузку атрибутом preload="none". По умолчанию браузер предзагружает начало видеофайла, значение none отключает это поведение и чуть ускоряет загрузку страницы. Но тогда уже при включении видео читателю придётся подождать чуть дольше.

<video controls preload="none" src="./about-us.mp4">
</video>

Встраиваемый плеер Эпл Музыки

Аудио

Вари­анты работы с аудио прин­ци­пи­ально не отли­ча­ются от видео.

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

Напри­мер, выбе­рем Share → Copy Embed Code у аль­бома в Эпл Музыке:

<iframe allow="clipboard-write" frameborder="0" height="450" style="width:100%;overflow:hidden;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/atom-heart-mother/1065975345">
</iframe>

Встраиваемый плеер Эпл Музыки

Аудио

Варианты работы с аудио принципиально не отличаются от видео.

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

Например, выберем Share → Copy Embed Code у альбома в Эпл Музыке:

<iframe allow="clipboard-write" frameborder="0" height="450" style="width:100%;overflow:hidden;" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-storage-access-by-user-activation allow-top-navigation-by-user-activation" src="https://embed.music.apple.com/us/album/atom-heart-mother/1065975345">
</iframe>

Можно хра­нить аудио­файлы у себя и исполь­зо­вать стан­дарт­ный плеер браузера:

<audio src="./audio.mp3" controls>
</audio>

Или исполь­зо­вать для созда­ния пле­ера сто­рон­нюю биб­лио­теку. Напри­мер, «Жуэль» Ильи Бирмана:

<a href="./dd-34.mp3" class="jouele">
  Думаем дальше № 34 — «Раньше вообще без штанов бегали» с Женей Арутюновым
</a>

Дизайн стандартного плеера отличается в разных браузерах и повлиять на него из ЦСС толком нельзя

Библиотеки дают простор для стилизации плеера и дополнительные функции, вроде объединения треков в плейлист.

Дизайн стандартного плеера отличается в разных браузерах и повлиять на него из ЦСС толком нельзя

Библиотеки дают простор для стилизации плеера и дополнительные функции, вроде объединения треков в плейлист.

Можно хранить аудиофайлы у себя и использовать стандартный плеер браузера:

<audio src="./audio.mp3" controls>
</audio>

Или использовать для создания плеера стороннюю библиотеку. Например, «Жуэль» Ильи Бирмана:

<a href="./dd-34.mp3" class="jouele">
  Думаем дальше № 34 — «Раньше вообще без штанов бегали» с Женей Арутюновым
</a>

Сайдноуты

Сайд­но­уты — это при­ме­ча­ния на полях. Убе­ди­тесь, что раз­мер полей доста­то­чен и поставьте туда сайд­но­уты абсолютом:

/* Абсолют и вынос на поля только когда позволяет ширина экрана */
@media (width > 768px) {
  .layer {
    --sideWidth: 5em;  /* Ширину сайдноута — в переменную, чтобы не повторяться */
    position: relative; /* Для работы абсолютного позиционирования внутри слоя */
    padding-right: calc(var(--sideWidth) + 1em); /* Поля больше ширины сайдноута, чтобы был отступ от текста */
  }

  .sidenote {
    width: var(--sideWidth);
    position: absolute;
    right: 0;
  }
}
<div class="layer">
    <p>…</p>

    <div class="sidenote">
      <a href="…">Интерфейс — зло</a>
    </div>

    <p>…</p>
    <p>…</p>
</div>

Бюро помо­гает созда­вать новые и улуч­шать суще­ству­ю­щие про­дукты и услуги.

Удоб­ный интер­фейс и кра­си­вая упа­ковка не гаран­ти­руют успех. В основе успеш­ного про­дукта — рабо­то­спо­соб­ная система. Мы иссле­дуем и исполь­зуем законы, при­ёмы и идеи рабо­то­спо­соб­ного дизайна, а пер­вое, о чём думаем — как обой­тись без интерфейса.

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

Бюро помогает создавать новые и улучшать существующие продукты и услуги.

Удобный интерфейс и красивая упаковка не гарантируют успех. В основе успешного продукта — работоспособная система. Мы исследуем и используем законы, приёмы и идеи работоспособного дизайна, а первое, о чём думаем — как обойтись без интерфейса.

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

Сайдноуты

Сайдноуты — это примечания на полях. Убедитесь, что размер полей достаточен и поставьте туда сайдноуты абсолютом:

/* Абсолют и вынос на поля только когда позволяет ширина экрана */
@media (width > 768px) {
  .layer {
    --sideWidth: 5em;  /* Ширину сайдноута — в переменную, чтобы не повторяться */
    position: relative; /* Для работы абсолютного позиционирования внутри слоя */
    padding-right: calc(var(--sideWidth) + 1em); /* Поля больше ширины сайдноута, чтобы был отступ от текста */
  }

  .sidenote {
    width: var(--sideWidth);
    position: absolute;
    right: 0;
  }
}
<div class="layer">
    <p>…</p>

    <div class="sidenote">
      <a href="…">Интерфейс — зло</a>
    </div>

    <p>…</p>
    <p>…</p>
</div>

У сайд­но­у­тов с абсо­лют­ным пози­ци­о­ни­ро­ва­нием есть проблема.

Чтобы на широ­ком экране они встали на поля, в раз­метке сайд­ноут дол­жен сто­ять над тек­стом, к кото­рому относится.

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

<div class="layer">
    <div class="sidenote">…</div>
    <p>…</p>
</div>

Самый про­стой спо­соб испра­вить это без про­грам­ми­ро­ва­ния — про­дуб­ли­ро­вать сайд­но­уты в раз­метке над и под тек­стом, на узком экране пря­тать один и пока­зы­вать другой.

В иде­але — запро­грам­ми­ро­вать сайт, чтобы он созда­вал такие копии сайд­но­у­тов автоматически.

<div class="layer">
    <div class="sidenote">…</div>
    <p>…</p>
    <div class="sidenote is__sm">…</div>
</div>
.sidenote:not(.is__sm) {
  display: none;
}

@media (width > 768px) {
  .sidenote.is__sm {
    display: none;
  }

  .sidenote:not(.is__sm) {
    display: block;
  }
}

Мы иссле­дуем и исполь­зуем законы, при­ёмы и идеи рабо­то­спо­соб­ного дизайна, а пер­вое, о чём думаем — как обой­тись без интерфейса.


Мы иссле­дуем и исполь­зуем законы, при­ёмы и идеи рабо­то­спо­соб­ного дизайна, а пер­вое, о чём думаем — как обой­тись без интерфейса.

Мы иссле­дуем и исполь­зуем законы, при­ёмы и идеи рабо­то­спо­соб­ного дизайна, а пер­вое, о чём думаем — как обой­тись без интерфейса.

Мы иссле­дуем и исполь­зуем законы, при­ёмы и идеи рабо­то­спо­соб­ного дизайна, а пер­вое, о чём думаем — как обой­тись без интерфейса.

Мы исследуем и используем законы, приёмы и идеи работоспособного дизайна, а первое, о чём думаем — как обойтись без интер­фейса.


Мы исследуем и используем законы, приёмы и идеи работоспособного дизайна, а первое, о чём думаем — как обойтись без интер­фейса.

Мы исследуем и используем законы, приёмы и идеи работоспособного дизайна, а первое, о чём думаем — как обойтись без интер­фейса.

Мы исследуем и используем законы, приёмы и идеи работоспособного дизайна, а первое, о чём думаем — как обойтись без интер­фейса.

У сайдноутов с абсолютным позиционированием есть проблема.

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

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

<div class="layer">
    <div class="sidenote">…</div>
    <p>…</p>
</div>

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

В идеале — запрограммировать сайт, чтобы он создавал такие копии сайдноутов автоматически.

<div class="layer">
    <div class="sidenote">…</div>
    <p>…</p>
    <div class="sidenote is__sm">…</div>
</div>
.sidenote:not(.is__sm) {
  display: none;
}

@media (width > 768px) {
  .sidenote.is__sm {
    display: none;
  }

  .sidenote:not(.is__sm) {
    display: block;
  }
}

Плашки

Плашка помо­гает отде­лить модуль от сосед­него и объ­еди­нить его соб­ствен­ные части. Оформ­ле­ние плашки — дань теку­щей моде или фир­мен­ному стилю, но её функ­ция оста­ётся одной и той же — выде­лять и объединять.

В вёрстке про­стей­шая плашка — про­сто кон­тей­нер с полями и фоном. Хоро­шая идея — одно­вре­менно «сло­мать» сетку и создать внутри плашки осо­бый ритм, чтобы допол­ни­тельно выде­лить её среди дру­гих модулей.

.boxed {
  padding: 36px 9px 72px;
  background: #f3f5f4;
}
<div class="boxed">
  <div class="cols">
    <!-- … -->
  </div>
</div>

О сетках:

Когда жерт­во­вать сро­ком, бюд­же­том и каче­ством нельзя, мето­дом исклю­че­ния при­хо­дим к одному: жерт­вуем функ­ци­ями, «флек­сим». Отре­заем то, что не успе­ваем сде­лать. Этот вари­ант — боль­шая удача:

Чем больше функ­ций, тем больше их пере­се­че­ний, а зна­чит мест для ошибки. Поэтому отказ от функ­ции помо­гает умень­шить коли­че­ство узких мест и повы­сить каче­ство пер­вой версии.

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

Когда отло­жен­ная функ­ция доба­вится во вто­рой вер­сии, будет кого ей пора­до­вать. Кроме того, новая функ­ция — это инфор­ма­ци­он­ный повод, лиш­няя при­чина рас­ска­зать о продукте.

Когда жертвовать сроком, бюджетом и качеством нельзя, методом исключения приходим к одному: жертвуем функциями, «флексим». Отрезаем то, что не успеваем сделать. Этот вариант — большая удача:

Открытый вовремя продукт помогает быстрее узнать правду о продукте: изучить мнение пользователей и проверить гипотезы.

Когда отложенная функция добавится во второй версии, будет кого ей порадовать.

Плашки

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

В вёрстке простейшая плашка — просто контейнер с полями и фоном. Хорошая идея — одновременно «сломать» сетку и создать внутри плашки особый ритм, чтобы дополнительно выделить её среди других модулей.

.boxed {
  padding: 36px 9px 72px;
  background: #f3f5f4;
}
<div class="boxed">
  <div class="cols">
    <!-- … -->
  </div>
</div>

О сетках:

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

Разбиваем проект на короткие итерации с регулярными пусками

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

Выносы и цитаты

Выносы и цитаты при­вле­кают вни­ма­ние. Если они инте­рес­ные — чита­телю захо­чется узнать подроб­но­сти в тек­сте. Вынос вер­стают обёрт­кой с осо­быми стилями:

<p>…</p>
<div class="liftOut">
  <p>…</p>
</div>
<p>…</p>
.liftOut {
  font-size: 200%;
  padding-left: 1.25em;
  color: #00aff0;
}

.liftOut:not(:last-child) {
  margin-bottom: 1.25em;
}

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

Разбиваем проект на короткие итерации с регулярными пусками

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

Выносы и цитаты

Выносы и цитаты привлекают внимание. Если они интересные — читателю захочется узнать подробности в тексте. Вынос верстают обёрткой с особыми стилями:

<p>…</p>
<div class="liftOut">
  <p>…</p>
</div>
<p>…</p>
.liftOut {
  font-size: 200%;
  padding-left: 1.25em;
  color: #00aff0;
}

.liftOut:not(:last-child) {
  margin-bottom: 1.25em;
}

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

Цитаты внутри тек­ста вер­стают тегом q.

Тег обо­ра­чи­вает содер­жи­мое в кавычки, сове­туем сразу отклю­чить это в ЦСС и рас­став­лять кавычки вруч­ную. Так проще рабо­тать с вло­жен­ными кавыч­ками: когда внутри цитаты есть слова в кавыч­ках, исполь­зуют дру­гой вид кавы­чек. А глав­ное — кавычки в тек­сте не поте­ря­ются при копи­ро­ва­нии, в отли­чие от ЦСС‑кавычек.

<p>
  М. М. Бахтин писал: <q>«Тришатов рассказывает подростку о своей любви к музыке и развивает перед ним замысел оперы: „Послушайте, любите вы музыку? Я ужасно люблю… Если бы я сочинял оперу, то, знаете, я бы взял сюжет из ,Фауста‘. Я очень люблю эту тему“»</q>.
<p>
q {
  /* Отключаем ЦСС-кавычки */
  quotes: none;

  /* Стилизуем по вкусу */
  font-style: italic;
}

Пример: Аркадий Мильчин и Людмила Чельцова. Справочник издателя и автора

М. М. Бахтин писал: «Тришатов рассказывает подростку о своей любви к музыке и развивает перед ним замысел оперы: „Послушайте, любите вы музыку? Я ужасно люблю… Если бы я сочинял оперу, то, знаете, я бы взял сюжет из ,Фауста‘. Я очень люблю эту тему“».

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

Цитаты внутри текста верстают тегом q.

Тег оборачивает содержимое в кавычки, советуем сразу отключить это в ЦСС и расставлять кавычки вручную. Так проще работать с вложенными кавычками: когда внутри цитаты есть слова в кавычках, используют другой вид кавычек. А главное — кавычки в тексте не потеряются при копировании, в отличие от ЦСС‑кавычек.

<p>
  М. М. Бахтин писал: <q>«Тришатов рассказывает подростку о своей любви к музыке и развивает перед ним замысел оперы: „Послушайте, любите вы музыку? Я ужасно люблю… Если бы я сочинял оперу, то, знаете, я бы взял сюжет из ,Фауста‘. Я очень люблю эту тему“»</q>.
<p>
q {
  /* Отключаем ЦСС-кавычки */
  quotes: none;

  /* Стилизуем по вкусу */
  font-style: italic;
}

Пример: Аркадий Мильчин и Людмила Чельцова. Справочник издателя и автора

М. М. Бахтин писал: «Тришатов рассказывает подростку о своей любви к музыке и развивает перед ним замысел оперы: „Послушайте, любите вы музыку? Я ужасно люблю… Если бы я сочинял оперу, то, знаете, я бы взял сюжет из ,Фауста‘. Я очень люблю эту тему“».

Для блоч­ных цитат‑вре­зок исполь­зуют тег blockquote:

<p>…</p>

<blockquote> 
  «Клиент ставит задачу профессионалу. Он заказывает услугу и ждёт хороший сервис. Человеческие отношения построены на ожиданиях, и первым делом нужно узнать — чего же именно ждёт клиент»
</blockquote>

<p>…</p>
blockquote {
  margin: 1em 0; /* У тега есть маржины по умолчанию, переопределим на свои */
  font-size: 150%;
  font-style: italic;
}

Под­пись к цитате можно свер­стать любым тегом. Назва­ние источ­ника цитаты лучше обер­нуть в тег cite для опти­ми­за­ции в поисковиках.

Для удоб­ства пози­ци­о­ни­ро­ва­ния, цитату с под­пи­сью поме­щают в обёртку. По смыслу подой­дёт тег figure и его figcaption.

Полу­чится чуть гро­моздко, зато надёжно и уни­вер­сально, а все теги под­хо­дят по смыслу, что хорошо для поис­ко­ви­ков и про­грамм чте­ния с экрана:

<p>…</p>

<figure class="quote">
  <blockquote>
    «Клиент ставит задачу профессионалу. Он заказывает услугу и ждёт хороший сервис. Человеческие отношения построены на ожиданиях, и первым делом нужно узнать — чего же именно ждёт клиент»
  </blockquote>

  <figcaption class="quote__caption">
    Артём Горбунов. <cite>«Что такое профессиональная этика?»</cite>
  </figcaption>
</figure>

<p>…</p>
/* Изолируем стили в классе */
.quote {
  /* Перебьём стандартные отступы figure */
  margin: 1em 0;
}

.quote blockquote {
  margin: 0;
  font-size: 150%;
  font-style: italic;
}

.quote__caption {
  margin-top: 0.43em;
  font-size: 0.777em;
}

/* По умолчанию тег cite добавляет курсив, почистим */
cite {
  font-style: inherit;
}

Для блочных цитат‑врезок используют тег blockquote:

<p>…</p>

<blockquote> 
  «Клиент ставит задачу профессионалу. Он заказывает услугу и ждёт хороший сервис. Человеческие отношения построены на ожиданиях, и первым делом нужно узнать — чего же именно ждёт клиент»
</blockquote>

<p>…</p>
blockquote {
  margin: 1em 0; /* У тега есть маржины по умолчанию, переопределим на свои */
  font-size: 150%;
  font-style: italic;
}

Подпись к цитате можно сверстать любым тегом. Название источника цитаты лучше обернуть в тег cite для оптимизации в поисковиках.

Для удобства позиционирования, цитату с подписью помещают в обёртку. По смыслу подойдёт тег figure и его figcaption.

Получится чуть громоздко, зато надёжно и универсально, а все теги подходят по смыслу, что хорошо для поисковиков и программ чтения с экрана:

<p>…</p>

<figure class="quote">
  <blockquote>
    «Клиент ставит задачу профессионалу. Он заказывает услугу и ждёт хороший сервис. Человеческие отношения построены на ожиданиях, и первым делом нужно узнать — чего же именно ждёт клиент»
  </blockquote>

  <figcaption class="quote__caption">
    Артём Горбунов. <cite>«Что такое профессиональная этика?»</cite>
  </figcaption>
</figure>

<p>…</p>
/* Изолируем стили в классе */
.quote {
  /* Перебьём стандартные отступы figure */
  margin: 1em 0;
}

.quote blockquote {
  margin: 0;
  font-size: 150%;
  font-style: italic;
}

.quote__caption {
  margin-top: 0.43em;
  font-size: 0.777em;
}

/* По умолчанию тег cite добавляет курсив, почистим */
cite {
  font-style: inherit;
}

Фактоиды

Мы назы­ваем фак­то­и­дом любую круп­ную цифру с под­пи­сью или вынос в духе «а зна­ете ли вы, что». Фак­то­иды могут здо­рово ожи­вить вёрстку, появ­ля­ясь на полях — в сайд­но­у­тах — или внутри содержимого.

Сове­туем сна­чала свер­стать фак­тоид «в ваку­уме» и лишь затем под­став­лять его вёрстку в нуж­ные места стра­ницы. Это помо­жет не запу­таться в коде и свер­стать надёж­ный модуль, кото­рый не сло­ма­ется при исполь­зо­ва­нии в дру­гом контексте.

<div class="fact">
  <span class="fact__number">0,85</span>
  <span class="fact__caption">…</span>
</div>
.fact {
  display: flex;
  flex-direction: column;
}

.fact__number {
  font-size: 85px;
}

.fact__caption {
  font-size: 14px;
}
0,85

Максимальный бонусный балл при поступлении в школу до конца ноября

Фактоиды

Мы называем фактоидом любую крупную цифру с подписью или вынос в духе «а знаете ли вы, что». Фактоиды могут здорово оживить вёрстку, появляясь на полях — в сайдноутах — или внутри содержимого.

Советуем сначала сверстать фактоид «в вакууме» и лишь затем подставлять его вёрстку в нужные места страницы. Это поможет не запутаться в коде и сверстать надёжный модуль, который не сломается при использовании в другом контексте.

<div class="fact">
  <span class="fact__number">0,85</span>
  <span class="fact__caption">…</span>
</div>
.fact {
  display: flex;
  flex-direction: column;
}

.fact__number {
  font-size: 85px;
}

.fact__caption {
  font-size: 14px;
}
0,85

Максимальный бонусный балл при поступлении в школу до конца ноября

Социокнопки

Соци­окнопки поз­во­ляют поде­литься стра­ни­цей в соц­се­тях и чатах.

Кнопки можно свер­стать самому, но при­дётся изу­чить нюансы настройки кнопки для каж­дой соцсети.

Быст­рее и проще взять гото­вое реше­ние. Напри­мер, в «Лайкли» Ильи Бир­мана созда­ние кно­пок мак­си­мально упро­щено: под­клю­чите биб­лио­теку, создайте эле­менты с осо­быми клас­сами и всё зара­бо­тает само. Есть несколько гото­вых тем оформ­ле­ния и воз­мож­ность самому сти­ли­зо­вать кнопки под вид сайта.

<script src="./likely.js"></script>
<link rel="stylesheet" href="./likely.css">

<!-- … -->

<div class="likely">
  <div class="telegram">Отправить</div>
  <div class="vkontakte">Поделиться</div>
  <div class="xcom"></div>
  <div class="facebook"></div>
  <div class="linkedin"></div>
  <div class="whatsapp"></div>
  <div class="viber"></div>
  <div class="pinterest">Запинить</div>
</div>
Отпра­вить
Поде­литься
Запи­нить

Социокнопки

Социокнопки позволяют поделиться страницей в соцсетях и чатах.

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

Быстрее и проще взять готовое решение. Например, в «Лайкли» Ильи Бирмана создание кнопок максимально упрощено: подключите библиотеку, создайте элементы с особыми классами и всё заработает само. Есть несколько готовых тем оформления и возможность самому стилизовать кнопки под вид сайта.

<script src="./likely.js"></script>
<link rel="stylesheet" href="./likely.css">

<!-- … -->

<div class="likely">
  <div class="telegram">Отправить</div>
  <div class="vkontakte">Поделиться</div>
  <div class="xcom"></div>
</div>
Отправить
Поделиться

Каталог товаров

Каталог товаров