Пишем понятный код с пользовательскими свойствами CSS

Перевод More Readable CSS with CSS Custom Properties – второй статьи Серджио Гомеса о пользовательских свойствах CSS

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

Важно: в статье для краткости опущены фолбэки при использовании var(), но для надёжности кода про них нужно помнить.

.foo {
  /* для браузеров, которые не поддерживают пользовательские свойства */
  color: black;
  /* black используется в случае, когда --color не определён */
  color: var(--color, black);
}

Придерживайтесь принципа DRY

DRY означает “Don’t Repeat Yourself” (Не Повторяйтесь). Этот принцип хорош тем, что экономит время и силы не только при наборе кода, но и при поиске, замене и отладке каждый раз, когда нужно внести изменения. Проще говоря, он уменьшает стоимость поддержки кода.

Вот пример плохого CSS со множеством повторений:

.button {
  background-color: gray;
}
 
.title {
  color: gray;
}
 
.image-grid > .image {
  border-color: gray;
}
 
.caption {
  color: gray; /* должен быть gray вне зависимости от темы */
}

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

Как могут помочь пользовательские свойства CSS? Просто задаём значение в одном месте и потом используем его много раз! Давайте создадим свойство --theme-color:

:root {
  --theme-color: gray;
}
 
.button {
  background-color: var(--theme-color);
}
 
.title {
  color: var(--theme-color);
}
 
.image-grid > .image {
  border-color: var(--theme-color);
}
 
.caption {
  color: gray;
}

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

Для цвета подписи тоже можно создать пользовательское свойство:

:root {
  --theme-color: gray;
  --caption-text-color: gray;
}
 
.button {
  background-color: var(--theme-color);
}
 
.title {
  color: var(--theme-color);
}
 
.image-grid > .image {
  border-color: var(--theme-color);
}
 
.caption {
  color: var(--caption-text-color);
}

Но это только начало!

Отложите калькулятор

Как вы уже знаете из введения в пользовательские свойства CSS, их можно использовать совместно с функцией calc().

Посмотрите на эту сетку:

img

.image-grid {
  padding: 8px;
}
 
.image-grid > .image {
  margin: 8px;
}

Если вы уже видели этот пример раньше или хорошо знакомы с блочной моделью, вы поймёте, что у нас получается раскладка с отступами 16px вокруг и между ячейками. Это не совсем очевидно из CSS и мы не выделяем вещи, о которых заботимся больше всего.

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

С помощью пользовательских свойств и calc() можем написать более понятно:

:root {
  --image-grid-spacing: 16px;
}
 
.image-grid {
  padding: calc(var(--image-grid-spacing) / 2);
}
 
.image-grid > .image {
  margin: calc(var(--image-grid-spacing) / 2);
}

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

Эту идею можно расширить и подстраивать все элементы под раскладку.

:root {
  --page-grid: 4px;
  --image-grid-spacing: calc(4 * var(--page-grid));
}
 
.title {
  font-size: calc(5 * var(--page-grid));
}
 
.paragraph {
  margin: calc(4 * var(--page-grid));
}
 
.image-grid {
  padding: calc(var(--image-grid-spacing) / 2);
}
 
.image-grid > .image {
  margin: calc(var(--image-grid-spacing) / 2);
}

В примере выше мы для ясности вводим промежуточные вычисления.

Важно: В Safari/WebKit такие вычисления пока не работают. Это уже исправлено в Safari Technology Preview, так что вероятно будет исправлено в следующем релизе Safari 10.1.

Понятные изменения

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

Например, вот простая сетка с помощью флексбоксов:

.image-grid {
  display: flex;
  flex-wrap: wrap;
  padding: 8px;
}
 
.image-grid > .image {
  margin: 8px;
  width: calc(100% - 16px);
}
 
@media (min-size: 600px) {
  /* 3 картинки в ряд */
  .image-grid > .image {
    width: calc(100% / 3 - 16px);
  }
}
 
@media (min-size: 1024px) {
  /* 6 картинок в ряд */
  .image-grid > .image {
    width: calc(100% / 6 - 16px);
  }
}

Здесь не сразу понятно что происходит, так что давайте разберёмся. По умолчанию, для маленьких экранов, это сетка с одной колонкой. При увеличении размера экрана колонок становится 3, а потом 6. Как и в примере из предыдущей части, вокруг и между ячейками отступ 16px.

Из-за сложности вычислений нам пришлось добавить комментарии. Но, если мы используем пользовательские свойства:

:root {
  --grid-spacing: 16px;
  --grid-columns: 1;
}
 
.image-grid {
  display: flex;
  flex-wrap: wrap;
  padding: calc(var(--grid-spacing) / 2);
}
 
.image-grid > .image {
  margin: calc(var(--grid-spacing) / 2);
  width: calc(100% / var(--grid-columns) - var(--grid-spacing));
}
 
@media (min-size: 600px) {
  :root {
    --grid-columns: 3;
  }
}
 
@media (min-size: 1024px) {
  :root {
    --grid-columns: 6;
  }
}

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

Важно: код выше может быть слишком сложным для препроцессоров. Учтите это, если используете какой-нибудь и что-то идёт не так.

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