От 66% до 185 пикселей: как браузеры определяют значения CSS перед формированием макета
Проследите за значением CSS на этапах его объявления, каскадирования, указания, вычисления, использования и фактического применения, чтобы понять, почему относительные единицы и трюк преобразования в rem ведут себя именно так.
Победа в каскадировании правил — это не конец пути объявления CSS. Даже если значение width: 50% превосходит все конкурирующие правила, браузер всё равно не может передать значение 50% своему движку макетирования; ему необходимо определить, что именно составляет пятьдесят процентов в текущем контексте, и преобразовать это в конкретный размер. В этом руководстве следуется за одним значением на каждом этапе обработки, определённом спецификацией, показано, где разрешаются относительные единицы вроде rem и проценты, а также объясняется популярный трюк преобразования px в rem вместе с связанными с ним ограничениями по доступности.
Значение, которое вы вводите, — это не то значение, которое будет использовано
Большая часть того, что вы вводите в таблицу стилей, представляет собой отношение, а не измерение. Ширина может быть долей ширины родительского элемента:
width: 50%;
или, в другом правиле, другой долей:
width: 66%;
Размер шрифта может быть относительным к размеру шрифта корневого элемента:
font-size: 2rem;
Высота может быть долей от размеров области отображения:
height: 50vh;
Ни одно из этих значений не указано в пикселях, тем не менее для формирования макета необходимы реальные размеры, чтобы определить местоположение каждого элемента. Чтобы преодолеть эту проблему, каждое значение проходит через фиксированную последовательность этапов:
Declared Value
↓
Cascaded Value
↓
Specified Value
↓
Computed Value
↓
Used Value
↓
Actual Value
На первый взгляд названия этих этапов кажутся жаргоном, но каждый из них отвечает на конкретный вопрос. Ниже приведено пошаговое описание всех этапов с использованием одного примера.
Заданное значение: то, о чем вы просили
Заданное значение — это просто то значение, которое указано в объявлении. Исходные данные:
width: 66%;
Заданное значение для width составляет 66%. Это всего лишь запрос, ничего больше. У элемента может быть несколько заданных значений для одного и того же свойства, полученных из разных правил, поэтому первая задача — выбрать одно из них.
Каскадируемое значение: то, что осталось
Представьте два правила, оба устанавливающих ширину абзаца. Одно из них использует селектор типа:
p {
width: 100px;
}
а другое — класс:
.amazing {
width: 66%;
}
Если абзац имеет класс amazing, оба правила применяются к нему. Селектор класса более специфичен, чем селектор элемента без указания класса, поэтому каскад выбирает:
width: 66%;
То оставшееся значение 66% и является каскадируемым значением. Этот этап важен, потому что все последующие шаги работают только с этим единственным победителем; правила, проигравшие конкуренцию, просто игнорируются, а не обрабатываются.
Указанное значение: обеспечение наличия значения у каждого свойства
Указанное значение — это результат гарантии того, что у каждого свойства на каждом элементе есть какое-то значение. Правило простое:
- если существует каскадируемое значение, оно становится указанным значением;
В нашем примере существует каскадирующее значение, поэтому указанное значение остается 66%, и на этом этапе, по-видимому, ничего не происходит. Это становится важным для свойств, для которых совсем не указано никаких правил, и именно здесь на сцену выходит наследование. Этот механизм рассматривается в последнем разделе.
Рассчитанное значение: определение того, что можно вычислить без форматирования страницы
Рассчитанное значение — это тот этап, когда браузер определяет всё, что можно вычислить, не форматируя страницу фактически. Относительные размеры, основанные на размерах шрифта, превращаются в абсолютные размеры, а ключевые слова — в конкретные значения. Рассмотрим следующий пример:
font-size: 2rem;
Единица измерения здесь:
2rem
Описывает соотношение «двойной размер шрифта корневого элемента», а не фиксированное значение. На этапе вычислений браузер определяет размер шрифта корневого элемента и преобразует его в пиксели. Если размер шрифта корневого элемента составляет 16 пикселей, то размер шрифта после вычислений будет 32 пикселя.
Одной из причин такого преобразования на этом этапе является наследование: именно вычисленное значение наследуют дочерние элементы, поэтому они получают конкретную длину вместо соотношения, которое им пришлось бы переосмысливать в собственном контексте. Именно поэтому размеры шрифта типа em, находящиеся внутри друг друга, умножаются между собой, и почему процентные значения высоты строки иногда приводят к тесному размещению текста в дочерних элементах с более крупным шрифтом.
Некоторые значения пока не могут быть определены. Ширина в процентах зависит от размера контейнирующего блока, который известен только после формирования макета, поэтому 66% обычно остается процентом в вычисленном значении и фиксируется только на следующем этапе.
Используемое значение: учет информации о макете
Используемое значение — это результат после того, как становится доступна информация о макете. Вернуться к:
width: 66%;
Сам по себе процент является неполным, пока не известно, часть от чего он представляет. Для width этим ориентиром является ширина контейнирующего блока. Предположим, что родительский раздел:
280px
имеет большую ширину. Теперь браузер может вычислить:
66% of 280px
что даёт следующий результат:
184.8px
Это значение является фактически используемым. Это поворотный момент в процессе: браузер больше не хранит указанный вами процент, а реальную размерность, полученную на основе фактической геометрии страницы. То же самое касается таких значений, как ширина auto, которые имеют смысл только после определения окружающей компоновки. Если вы запрашиваете свойства элемента в JavaScript с помощью getComputedStyle, многие свойства, зависящие от компоновки, такие как width, возвращают именно это окончательное значение в пикселях, а не процент, что является удобным способом наблюдения за процессом на практике.
Фактическое значение: соответствие ограничениям устройства
На последнем этапе учитываются ограничения среды отображения. Устройство не всегда может отрисовать значение с произвольной точностью, поэтому фактически используемое значение может выглядеть примерно так:
184.8px
и в итоге отображаться примерно так:
185px
Это скорректированное значение является фактической величиной. На практике современные браузеры отображают элементы с точностью до подпикселя и применяют округление или зацепление в разных моментах в зависимости от свойства и движка, поэтому не стоит полагаться на какое-либо конкретное правило округления. Главная идея заключается просто в том, что может произойти окончательная корректировка между рассчитанной величиной и тем, что отображается на экране.
Если говорить более общо, вся эта последовательность сводится к одному предложению: то, что вы пишете в стилевом листе, не обязательно будет использовано браузером, и между этим существуют четко определенные этапы.
Почему относительные единицы становятся менее загадочными
Эта модель объясняет, почему относительные единицы настолько ценны и почему их так часто неправильно понимают. Такие единицы, как:
%
rem
em
vh
vw
Это позволяет описывать размеры с использованием других параметров (размера родительского элемента, основного размера шрифта, собственного размера шрифта элемента или области отображения), вместо жесткой привязки к пикселям. Именно это обеспечивает адаптацию макетов к разным экранам и настройкам пользователя.
Однако каждый из них должен быть приведён к своему исходнику перед тем, как лейаут сможет им воспользоваться, причём это происходит на разных этапах: единицы, относительные к шрифту, — на этапе вычислений, а проценты — к содержащему блоку на этапе использования. Учитывая этот процесс, можно заменить расплывчатую идею о том, что «браузер всё решит сам», более конкретным вопросом во время отладки: от чего зависит эта ценность и каков в данный момент её исходник? Когда адаптивный лейаут ведёт себя некорректно, причиной часто бывает исходник, отличный от того, что вы предполагали, например процент, приводимый к неожиданному содержащему блоку, или единица em, умножаемая через несколько вложенных элементов.
Преобразование px в rem с корнем 10px
Понимание обработки значений также помогает прояснить распространённый подход к настройке размеров: преобразование значений пикселей из дизайна в rem. Предположим, что в дизайне указано:
padding: 30px;
и вы предпочитаете выразить это следующим образом:
padding: 3rem;
Популярным решением является установка размера шрифта корня в целое число:
html {
font-size: 10px;
}
После этого:
1rem = 10px
преобразования превращаются в умственные вычисления:
30px → 3rem
40px → 4rem
20px → 2rem
В целом:
rem = pixels / 10
Преимущество заключается не только в более аккуратных числах. Поскольку каждый rem определяется относительно размера шрифта корня на этапе вычислений, изменение этого значения мгновенно пересчитывает все размеры, основанные на rem, на странице, что позволяет осуществлять единый глобальный контроль над системой размеров.
Существует важное ограничение. Установка значения корня равным фиксированному 10px переопределяет размер шрифта по умолчанию, который пользователь мог выбрать в настройках браузера, что подрывает одну из основных причин использования rem с точки зрения доступности. Широко используемой альтернативой является html { font-size: 62.5%; }: при стандартном значении 16px в браузере это также даёт 10px, но размер шрифта масштабируется пропорционально, когда пользователь увеличивает желаемый размер. Если вы будете использовать этот подход, не забудьте также установить читаемый размер шрифта для элемента body (например, 1.6rem), поскольку в противном случае текст будет использовать уменьшенный размер, заданный для корня.
Когда ничего не было объявлено
Один вопрос остаётся без ответа на предыдущих этапах. Рассмотрим следующий маркинг:
<div class="parent">
<p>Hello World</p>
</div>
и правило для обёртки:
.parent {
color: red;
}
Нигде нет правила вроде:
p {
color: red;
}
Однако текст параграфа красный. У параграфа нет иерархически заданного свойства color, поэтому указанное значение должно поступать откуда-то ещё. Поскольку color является наследуемым свойством, браузер использует рассчитанный цвет родителя. Это и есть наследование, которое напрямую связано с этапом указания значений, описанным ранее. Чтобы подробнее узнать, какие свойства наследуются, почему дочерние элементы получают рассчитанные значения вместо объявленных, и как свойства inherit и initial переопределяют стандартные значения, наследованию заслуживает отдельного рассмотрения.
Основные выводы
- Значение в CSS проходит через шесть этапов: объявленное, иерархическое, указанное, рассчитанное, используемое и фактическое.
- Иерархия свойств приводит множество объявленных значений к одному иерархическому значению; далее обрабатывается только это значение.
rem и em, преобразуются в абсолютные значения на этапе вычислений; процентные ширины определяются после формирования макета на этапе использования.62.5% лучше учитывает предпочтения пользователя в отношении шрифта, чем фиксированное значение 10px.