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

Содержание:
Абсолютные единицы измерения
Относительные единицы измерения

В CSS существует множество единиц измерения. Обычно мы используем единицы размеров, но также есть единицы для углов — например, deg и turn, единицы времени — s и ms, единицы плотности экрана — например, dpi и dppx и другие.

Здесь будут рассматриваться только единицы размеров, которыми мы пользуемся чаще всего. Подробное описание можно найти в спецификации W3C Distance Units: the <length> type.

Общим для всех единиц длины будет то, что для значения 0, единицы можно не указывать: height: 0px и height: 0 будут работать одинаково, так что единицы измерения можно отбросить. Это позволяет немного быстрее писать код и считывать значения свойств.

Абсолютные единицы измерения

Спецификация

cm сантиметр 1cm = 96px/2.54 ≈ 37,795px
mm миллиметр 1mm = 1/10 от 1cm
q четверь миллиметра 1q = 1/40 от 1cm = 0.25mm ≈ 0.945px
in дюйм 1in = 2.54cm = 96px
pc пика 1pc = 1/6 от 1in
pt пункт, точка 1pt = 1/72 от 1in
px пиксель 1px = 1/96 от 1in, 0.75 от pt

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

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

Я попыталась разными способами получить полоску длиной 10 сантиметров. Все полоски равны друг другу, но ни одна не равна 10 сантиметрам, если приложить линейку к экрану. Проверила на двух мониторах: на одном они короче, на другом — длиннее.

На веб-страницах физические единицы измерения вроде cm, mm и in не будут работать как ожидается, потому что главная экранная единица измерения — пиксель.

Что такое пиксель и какого он размера? Чем отличаются пиксели px от точек pt? Почему у них такие странные размеры в 1/96 дюйма и 1/72 дюйма соответственно? Как были выбраны эти значения и почему этих единиц измерения две? Попытка разобраться с мелкой точкой на экране вызывает множество вопросов и требует погружения в историю. Я попыталась выяснить как всё было, и вот что узнала.

Один из первых макинтошей

Когда появились первые программы для предпечатной подготовки, нужно было как-то соотнести размер элементов на экране с тем, чтоб будет напечатано на бумаге. Тогда оказалось, что один дюйм (inch) на экране соответствует 72 точкам. Это соотношение зафиксировано в pt, pt — это 1/72 дюйма. Изначально это была типографская единица измерения, и её значения менялись со временем, но с появлением компьютерных программ для полиграфии значение pt пришлось стандартизировать.

С развитием технологий стало возможно делать экраны с большим разрешением, и Microsoft предложила отображать 96 точек на дюйм, что было зафиксировано в px, то есть пиксель — это 1/96 дюйма. Это позволило увеличить чёткость мелкого текста, потому что для отображения символа теперь можно было использовать больше точек.

Размер px составляет 0.75 от pt. По сути, pt — это более крупный пиксель.

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

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

При этом важно помнить, что раз CSS-пиксели уже не соответствуют физическим точкам экрана, сантиметры и дюймы при выводе на экран тоже не будут не соответствовать своим реальным размерам, об этом есть и в спецификации:

If the anchor unit is the pixel unit, the physical units might not match their physical measurements. Alternatively if the anchor unit is a physical unit, the pixel unit might not map to a whole number of device pixels.

То есть, если использовать дюймы для вёрстки веб-страниц, один дюйм всегда будет равен 96 пикселям, но никогда — реальной физической единице:

Таким образом, главное в абсолютных единицах — контекст использования:

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

Пока копалась в истории единиц измерения, выяснилось, что во внутреннем коде Gecko когда-то была поддержка километров, удалили в 2009-м:

Bugzilla: remove internal code for unsupported non-CSS units (feet, miles, meters, kilometers, didots, ciceros)

Из физических единиц ещё может представлять интерес q: это относительно новая единица, и она поддерживается не всеми браузерами. q — это 0.25mm. Как и другие физические единицы, больше имеет смысл для печати, но также можно попытаться использовать её для уменьшения размера кода: q — это примерно 0.945px, то есть в некоторых случаях вполне можно использовать её вместо пикселей, получается один символ (q) вместо двух (px).

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

The Creation of Adam (#singlediv) | @iamvdo

Чистое безумие, конечно. Картина воспроизведена с помощью box-shadow, и мне кажется, тему рисования на CSS на этом можно закрывать.

Вес стилей демо — 4.5Mb, а если бы там вместо q были пиксели, стили весили бы на 300Kb больше.

Относительные единицы измерения

Спецификация

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

Единицы измерения, привязанные к шрифту

em размер шрифта элемента
ex высота x в нижнем регистре
ch ширина 0 (ZERO, U+0030)
rem размер шрифта корневого элемента

em

Для font-size это унаследованный размер шрифта, для остальных свойств — текущий размер шрифта, уже вычисленный для font-size.

Чтобы увидеть это вживую, возьмем такой код:

BODY {
  /* Базовый размер шрифта */
  font-size: 42px;
}

DIV {
  /* Наследуем шрифт родителя и уменьшаем в два раза */
  font-size: .5em;
}

/* Какой толщины будет border в каждом случае? */
.box-1 {
  border-width: .5em;
}
.box-2 {
  border-width: 1em;
}

Получилось вот что:

Розовая полоса — градиент высотой 1em, чтобы было с чем сравнивать.

Оба блока имеют одинаковый размер шрифта, уменьшенный относительно родительского элемента в два раза (font-size: .5em;). И как теперь указать толщину рамки равной размеру шрифта?

border-width: .5em делает рамку в два раза тоньше, чем нужно. Это происходит потому, что родительский размер шрифта использует только font-size, а border получает вычисленное значение из font-size.

Таким образом, если где-то не в font-size нужно использовать текущий размер шрифта, не нужно копировать значение размера, достаточно указать 1em. У правого блока рамка правильной толщины.

Ещё одно демо, для понимания как соотносятся em и символы шрифта. Цветные полосы имеют высоту 1em, поэтому видно, что 1em примерно соответствует высоте символов с учётом заглавных букв и выносных элементов:

Размер em нигде не зафиксирован, и вычисляется в момент использования на основе размера шрифта родителя. Например, если задать размер шрифта вот таким образом:

DIV {
  font-size: .75em;
}

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

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

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

ex

ex — это высота буквы x в нижнем регистре. Если в шрифте нет подходящей метрики, и в нём нет такого символа, браузер попробует вычислить ex самостоятельно. Если это по каким-то причинам невозможно, ex считается равным .5em.

В демо цветные полосы имеют высоту 1ex, и для выбранных шрифтов 1ex будет равен высоте маленькой x:

Посмотрим, как на ex влияет шрифт, и как ex соотносится с em.

В этом демо квадратикам в левой группе заданы размеры в 1em, в правой — в 2ex, так можно проверить равен ли ex половине em. Также каждому квадратику задан свой шрифт:

В отличие от em, размер ex будет меняться вместе со шрифтом, и во всех случаях 2ex не равно 1em, то есть на соотношение в .5 полагаться нельзя.

ex точно также как и em наследует размер шрифта родителя:

ch

ch — ширина символа 0. Для моноширинных шрифтов это точная ширина любого символа, для остальных — примерная ширина одного узкого символа. Если по каким-то причинам ширину невозможно вычислить, запасным значением будет .5em.

Эрик Мейер предостерегает от попыток использовать ch для задания ширины контейнера в символах, потому что это не работает как ожидается. В демо ниже ширина каждого блока с текстом задана вот таким образом:

DIV {
  width: 10ch;
}

Ширина блока будет верной только для моноширинных шрифтов (см. Courier), в некоторых шрифтах будет работать и для цифр (Arial, Comic Sans), в остальных случаях нельзя рассчитывать, что 1ch будет равен ширине символа:

Сопоставим 1em, 2ex и 2ch:

Очевидно, что:

  • 1ch больше 1ex (ширина 0 больше высоты x);
  • 1ch не равен половине em;
  • значение ch может меняться вместе со шрифтом.

rem

rem — это root em, размер шрифта корневого элемента, для веб-страницы это элемент html. Размер шрифта по умолчанию — 16px. Это значение не зафиксировано в спецификации, но используется всеми браузерами. Про историю вопроса можно почитать в рассылке W3C.

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

Важно понимать, что размер rem можно переопределить только для элемента html. Например, возьмем такие стили:

BODY {
  font-size: 24px;
}

DIV {
  font-size: 1rem;
}

Если rem можно было бы переопределять в любом месте, текст бы увеличился, но этого не произошло:

Высота цветной полосы 24px, чтобы было с чем сравнивать.

Если переопределить размер шрифта для элемента html, всё сработает:

HTML {
  font-size: 24px;
}

DIV {
  font-size: 1rem;
}

В отличие от em, rem всегда содержит размер шрифта только корневого элемента, поэтому вложенность ни на что не влияет:

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

Единицы измерения, привязанные к размерам экрана

vw 1% ширины вьюпорта
vh 1% высоты вьюпорта
vmin 1% от меньшего из vw и vh
vmax 1% от большего из vw и vh

Эти единицы предназначены для создания элементов, размер которых должен зависеть от размера окна (вьюпорта).

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

Размеры картинок задаются вот таким образом:

IMG {
  height: 100vh;
  width: 100%;
  object-fit: cover;
}

Ширина вьюпорта — 100vw. Это значение позволяет растянуть на ширину экрана любой элемент при любой вложенность.

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

Правда, тут есть проблема: 100vw — это ширина всего окна вместе с полосой прокрутки, а доступная для контента ширина окна полосу прокрутки не включает, из-за чего при попытке задать элементам ширину вьюпорта появится горизонтальный скролл:

Если нет возможности изменить вёрстку, чтобы избежать использования 100vw, можно задать overflow-x: hidden ближайшему родителю, растянутому на ширину страницы:

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

Если вы верстаете на MacOS, и полосы прокрутки исчезают сами по себе, выберите в System Perefences/General опцию «Показывать всегда», это позволит верстать страницы сразу с учетом сколлбаров:

Включить отображение полосы прокрутки на MacOS

Используя vw и vh можно делать полностью резиновые элементы, которые будут сами подстраиваться под размер окна, например, так:

.text {
  padding: 5vh 5vw;
  background: paleturquoise;
  background-image: repeating-linear-gradient(-45deg,
    rgba(255,255,255, .25), rgba(255,255,255, .25) 2vw,
    transparent 0, transparent 4vw
  );
  font-size: 12vw;
}

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

С помощью vmin можно сделать элемент, который всегда будет целиком помещаться в экран, сохраняя пропорции:

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

В отличие от единиц, привязанных к шрифту, единицы вьюпорта не реагируют на масштабирование страницы. Откройте это демо и поизменяйте масштаб используя Ctrl+/Ctrl-. Текст, размер которого задан в rem, будет увеличиваться и уменьшаться, текст с размером, заданным в vw, останется неизменным.

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

В качестве решения предлагается примешивать единицы вьюпорта к относительным единицам измерения, привязанным к размеру шрифта, например, так:

html {
  font-size: calc(1em + 0.5vw);
  line-height: calc(1.1em + 0.5vw);
}

Вживую можно потестить здесь, пример взят из этой статьи. Больше про управление размером шрифта с помощью единиц вьюпорта можно почитать тут.

За дополнение про особенности масштабирования спасибо @bekharsky.

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

В спецификации описано немного больше единиц измерения, например:

  • cap — высота заглавной буквы
  • lh — высота строки
  • rlh — высота строки корневого элемента

Но в данный момент они нигде не поддерживаются, поэтому в статье не рассматриваются.

Спасибо Илье Стрельцыну за полезные ссылки.

Ссылки по теме:
Distance Units: the <length> type
CSS <length>
Point (typography)
platform-specific font size issuesMailing lists, 15 Dec 1998
Making pt a non-physical unitMailing lists, 6 Jan 2010
[CSS21] Issue 149 - px vs. ptMailing lists, 16 Jun 2010
em, px, pt, cm, in…
What is the CSS ‘ch’ Unit?
The Lengths of CSS
CSS Length Explained
The Myth of DPI
CSS Units
Font sizing with rem
Новые и старые единицы измерения (краткий обзор)
CSS Viewport Units: A Quick Start
Fluid Typography
Fun with Viewport Units
Simple Little Use Case for `vmin`
Генератор цветовых темНедоступность в картинках
Наверх