Делать сайты я учился по-старомодному: открывал исходный код и пытался повторить то, что видел. А по тому, чего не видел (PHP/MySQL), прочёл какую-то случайно попавшуюся книгу. Так всё и шло. Это было в 1999 году, когда можно было писать что-то вроде <font size=”4" color=”#000000">, а DHTML приобретал популярность.

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

Block, inline и inline-block

Хотя мне было известно об этих свойствах, их глубокого понимания у меня не было. Вот основная информация:

  • блочные (block) элементы меняют размер в горизонтальной плоскости, занимая всю ширину строки (например, заголовок). Таким элементам можно задать вертикальный margin.
  • строчные (inline) элементы меняют размер в горизонтальной плоскости ровно настолько, чтобы вместить свой контент (например, strong и em). Таким элементам нельзя задать вертикальный внешний отступ (margin), и обычно они размещаются внутри блочных элементов.
  • строчно-блочные (inline-block) элементы похожи на строчные, но им можно задать вертикальный внешний отступ (margin). Такие элементы полезно применять, например, для оформления кнопок.

В примере ниже у блочных элементов синие границы, у строчных — оранжевый фон, а у строчно-блочного — красные границы:

Изображение — это строчный элемент

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

img {
display: block;
}

Такое объявление делает поведение изображений гораздо более предсказуемым. Также можно задать изображениям max-width: 100%;, чтобы они не выходили за пределы контейнера.

Расчёт высоты и ширины

По умолчанию высота (height) и ширина (width) контейнера вычисляется путём сложения следующих измерений:

  • область контента (content);
  • область внутренних отступов (padding);
  • область границ (border).

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

.some-class {
width: 50%;
padding: 2em;
border: 0.1rem;
}

Тогда полная ширина .some-class рассчитывалась бы таким образом: 50% + 4em + 0.2rem. Так происходит из-за свойства box-sizing, которое по умолчанию имеет значение content-box. Это значит, что свойство width относится только к области контента, всё остальное идёт дополнительно. Значение box-sizing можно изменить для всех элементов, вот так:

* {
box-sizing: border-box;
}

Возвращаясь к примеру, ширина элемента теперь включает и его границы, поэтому полная ширина в нашем случае равна 50%.

А что с border и padding? Эти свойства всё ещё применяются, но не влияют на полную ширину элемента — для них определена отдельная область. Вот, что это означает на практике:

Margin

Мы не обсуждали здесь margin, поскольку margin — это отступ между элементами. То есть его не нужно включать в расчёт ширины по определению.

Padding и margin — не одно и то же

Если у элемента нет фона и границ, может показаться, что padding и margin — это одно и то же, но это не так.

  • margin — отступ между элементами
  • padding — отступ внутри элемента

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

Схлопывание внешних отступов

Внешние отступы схлопываются, и это долго расстраивало новичков в CSS. Рейчел Эндрю описывает это поведение так:

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

Если у нас есть два блочных элемента, один из которых имеет свойство margin-bottom: 1em, а второй  —  ниже первого  —  имеет свойство margin-top: 1.5em), то отступ между этими элементами равен 1.5em. Смотрим пример ниже:

Зная об этой особенности, мы можем быстро и просто вычислять внешние отступы. Понимание схлопывания может изменить и наш подход к отступам в целом — здесь может пригодиться что-то вроде селектора “лоботомированной совы”: * + *.

Примечание: внешние отступы не схлопываются, когда у родительского элемента есть свойство display: grid или display: flex.

Браузерные стили по умолчанию

CSS расшифровывается как Cascading Style Sheets  —  каскадные таблицы стилей. Неудивительно, что каскадность  —  один из основных принципов CSS. 

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

Стили по умолчанию отличаются от браузера к браузеру, но именно они  —  причина того, что:

  • заголовки имеют разные размеры;
  • цвет текста  —  чёрный;
  • маркеры списков  —  жирные точки;
  • у всех элементов есть какое-то значение свойства display(например, block или inline).

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

Везде применяйте относительные единицы

Пиксели (px) заманчивы, их просто понять: объяви font-size: 24px  —  и размер текста станет равен 24px. Однако опыт взаимодействия с таким интерфейсом будет не самым лучшим, особенно для пользователей, самостоятельно меняющих размеры контента в браузере.

Я довольно быстро начал пользоваться em, а позже и rem для указания размера шрифта. Чтобы привыкнуть к em и rem в других элементах (padding, margin, letter-spacing и border), понадобилось гораздо больше времени.

Понимание разницы между em и rem — ключевой фактор в управления относительными единицами. К примеру, em можно использовать для медиа-запросов (@media) и вертикальных внешних отступов (margin), а rem для постоянной ширины границы(border-width).

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

::before и ::after требуют наличия content

При использовании псевдоэлементов ::before и ::after необходимо указывать свойство content, даже если оно не имеет значения:

.some-class::before {
content: '';
}

Если не указать content, псевдоэлемент просто не показывается на странице.

Единица ch

Чтобы задать элементу ширину, основываясь на примерном количестве символов в строке, полезно знать о единицу ch (character). Почему на примерном количестве? Потому, что ch не вычисляет число символов в строке. Основа этой единицы  —  ширина символа 0. Вот, что писал об этом Эрик Мейер:

“1ch обычно шире средней ширины символа примерно на 20–30%”.

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

Нормальный поток

Этот термин я часто слышал, но долгое время не понимал в полной мере. “Нормальный поток” означает, что элементы на странице появляются в том же порядке, в каком они стоят в коде. Например, напишем:

<h2>Heading</h2>
<p>Paragraph text.</p>

В этом случае мы ожидаем, что <h2>Heading</h2> появится перед/над <p>Paragraph text.</p>. Это и есть нормальный поток.

Если же элемент вырывается из нормального потока, это значит, что он не появится в ожидаемом месте. Элементы, позиционированные с помощью float или position: absolute, — хорошие тому примеры.

Стилизация состояний

О псевдоклассах :hover:focusи :active я узнал в контексте стилизации ссылок. В то время все примеры, которые я видел, выглядели примерно так:

a {
color: black;
}

a:hover,
a:focus,
a:active {
color: red;
}

Но будет лучше, если мы стилизуем состояние :focus по-другому.

:focus — это состояние, когда пользователь передвигается по фокусируемым элементам (например, ссылкам) с помощью клавиши табуляции. Когда пользователь нажимает TAB, он не знает, где окажется фокус. Кроме того, если пользователь сфокусируется на элементе, на который уже навёл курсор, он не узнает, где находится фокус. По этим причинам :focus лучше стилизовать отдельно от :hover и :active::

a:hover,
a:active {
/* styles */
}

a:focus {
/* styles */
}

:nth-child()

Взгляните на этот пример

Заметили, что фон задан у нечётных строк? Учитывая селектор (p:nth-child(even)), мы могли ожидать, что фон будет у чётных.

Однако селектор :nth-child() считает все сестринские элементы. Указание элемента в селекторе (например, p:nth-child()) вовсе не означает, что селектор начнёт считать с первого элемента именно этого типа. Напротив, правило будет применяться только к элементу такого типа. Если мы перепишем правило на p:nth-child(odd), то увидим, что:

  • h1 не имеет фона, хотя это нечётный сестринский элемент;
  • элементы p, которые соответствуют критерию (второй, четвёртый, шестой), имеют фон.

Возвращаясь к первому примеру, давайте представим, что мы хотим, чтобы фон был у чётных элементов p. В этом случае лучше подойдёт другой псевдокласс  — p:nth-of-type(even).

Этот пример показывает ключевое различие между :nth-child() и :nth-of-type(). Разница едва заметна, но знание о ней помогает избегать путаницы.

Заключение

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

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Things I Wish I’d Known About CSS

Предыдущая статьяЧто нового в системной трассировке Android Studio
Следующая статьяGo скучный. И это здорово!