getComputedStyle: какой стиль на самом деле

изменение≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

У элемента может не быть ни одного собственного стиля — и при этом он зелёный, с отступом в десять пикселей и шрифтом от родителя. element.style об этом промолчит: он показывает только атрибут style. Спросить браузер, что получилось на самом деле, умеет getComputedStyle.

Разделы темы:·

Заданный стиль разбросан по многим местам: внешний файл, тег <style>, атрибут style, стили самого браузера. Вычисленный существует в единственном экземпляре — один набор значений на элемент, итог каскада, специфичности, наследования и значений по умолчанию. Именно он определяет, как элемент выглядит.

getComputedStyle(element) возвращает этот итог: объект, у которого любое CSS-свойство спрашивают по camelCase-имени — color, fontSize, paddingTop.

getComputedStyle — метод window, а не элемента. element.getComputedStyle() не существует, хотя рука тянется написать именно так.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера
js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

element.style — не «стиль элемента», а разобранный атрибут style. Нет атрибута — нет и значений, сколько бы правил ни оформляло элемент. Это не баг и не недоделка: объект честно отражает ровно одну строчку разметки.

Зато писать в него удобно: свойство сразу попадает в атрибут и получает в каскаде вес выше любого селектора (кроме !important). Обратная сторона — читать оттуда можно только то, что вы туда сами положили.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Вычисленный стиль — уже посчитанный. em и rem превращены в пиксели, ключевые слова приведены к каноническому виду, цвета — к rgb() или rgba(). Браузеру так удобнее, и вам обычно тоже: не нужно самому знать размер шрифта родителя.

Цена — потеря исходной записи. Строка "10px" не помнит, что в CSS стояло 0.5em, а "rgb(255, 0, 0)" не помнит слова red. Вычисленный стиль годится, чтобы узнать, но не чтобы «вернуть как было».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера
js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

То, что отдаёт getComputedStyle, спецификация называет resolved value. Для большинства свойств это вычисленное значение, а для размеров и координат — использованное: то, что реально получилось после раскладки. Поэтому width: 50% вернётся пикселями.

Отсюда неожиданность: у элемента с display: none раскладки нет, считать нечего — и вместо пикселей приходит объявленное значение, чаще всего auto.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера
Что спрашиваемЧто приходит
width, heightпиксели после раскладки — если элемент отрисован
left, topпиксели у позиционированных элементов
fontSize, lineHeightпиксели, проценты и em уже пересчитаны; lineHeight без явного значения — слово normal, а не число
color, backgroundColorrgb(...) или rgba(...)
display, positionканоническое ключевое слово: none, block, flex
margin, background и другие сокращенияпо-разному в разных браузерах — спрашивайте marginTop, backgroundColor

Псевдоэлементов нет в DOM: textContent их не видит, querySelector не находит. Но стиль у них есть, и вторым аргументом getComputedStyle принимает строку с псевдоэлементом.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

CSS-переменные живут отдельно от обычных свойств: поля accent у объекта нет, читать нужно через getPropertyValue("--accent"). Значение приходит почти как написано в CSS — вплоть до пробела в начале, поэтому привыкайте к .trim().

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Первое ограничение — объект только для чтения. Попытка записать в него не «тихо не сработает», а бросит исключение. Менять стиль по-прежнему через element.style или переключение классов.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Второе — объект живой. Это не снимок, а окно в текущее состояние: сохранили его в переменную, поменяли элемент — и то же самое поле отдаёт уже новое значение. Удобно, но означает, что каждое чтение может стоить браузеру пересчёта.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

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

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Лечится разделением: сначала все чтения, потом все записи. Ещё дешевле — не читать вовсе, а держать нужное состояние в JavaScript и переключать классы.

Размеры и координаты точнее и дешевле берутся из element.getBoundingClientRect() — там дробные пиксели и учтены трансформации; offsetWidth и offsetHeight дают числа, а не строки, которые надо разбирать. Почему для измерений CSS не годится и чем offsetWidth отличается от clientWidth и scrollWidth, разбирает статья «Размеры элемента: offset, client, scroll».

Условие медиазапроса проверяет window.matchMedia("(min-width: 600px)").matches, реальную видимость элемента — element.checkVisibility(). И ещё: getComputedStyle не скажет, какое правило победило и из какого файла пришло — это работа инструментов разработчика.

Вместе getComputedStyle и getBoundingClientRect() работают в приборе пролога — необязательной части учебника о HTML и CSS. Примеры там печатают замеры строками вроде .box → width: 100px, а меряет за них подложка, которую на странице не видно. Как ею пользоваться, объясняет первая статья пролога, а здесь её можно прочитать изнутри. Вот функция, которой measure("адрес", "свойство") достаёт значение; stage в ней — сцена, элемент, в который пример кладёт свою разметку:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Размеры и координаты прибор берёт у getBoundingClientRect(), округляя до сотых: x и y — разность прямоугольников элемента и сцены, то есть расстояние от края сцены, а не от окна. Свойства CSS отдаёт getComputedStyle, поэтому цвета в прологе печатаются как rgb(…), а размеры шрифта — пикселями. Имя свойства прибор получает таким, как оно пишется в CSS, и getPropertyValue("margin-top") его понимает, а getPropertyValue("marginTop") вернул бы пустую строку. Проверка CSS.supports(prop, 'inherit') отличает свойство CSS от прочих имён: значение inherit принимает любое свойство. Для checked или returnValue она ложна, и прибор читает одноимённое свойство самого элемента.

  • element.style — зеркало атрибута style: пусто, пока вы сами туда не написали, сколько бы правил элемент ни оформляло.
  • getComputedStyle(element) отдаёт итог каскада — один набор значений в канонической форме, по camelCase-именам свойств.
  • Длины приходят пикселями, цвета — как rgb(...), ключевые слова нормализованы: обратно в 0.5em или red это не превратить.
  • Для размеров и координат возвращается значение после раскладки, а у display: none раскладки нет — будет auto.
  • Объект живой и только для чтения: менять стиль — через element.style или классы, а частые чтения после правок заставляют браузер пересчитывать раскладку.

1/3Элемент оформлен правилом .card { color: red; }, атрибута style у него нет. Что вернёт card.style.color?

2/3У родителя font-size: 20px, у потомка — font-size: 0.5em. Что вернёт getComputedStyle(child).fontSize?

3/3Что произойдёт при getComputedStyle(el).setProperty("color", "red")?

Скрыт ли элемент

Напишите isHidden(element): true, если элемент не виден из-за display: none, visibility: hidden или нулевой прозрачности — неважно, задано это классом или атрибутом style.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Все три причины видны в вычисленном стиле — а откуда они пришли, класс это или инлайн, уже неважно:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Прозрачность приходит строкой ("0"), поэтому сравниваем через Number. Проверка честная, но не всесильная: у потомка скрытого родителя display останется своим ("block"), хотя на экране его нет. Для такого случая есть element.checkVisibility() и element.offsetParent === null.

Жирный ли шрифт

Насыщенность шрифта задают то словом, то числом. Напишите isBold(element), который отвечает true при насыщенности 700 и выше — независимо от формы записи в CSS.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
страница примера

Вычисленный стиль уже привёл bold к числу — но отдал его строкой, так что сравнивать нужно числа:

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Сравнивать строки нельзя: "1000" >= "700" даёт false, потому что строки сравниваются посимвольно и "1" меньше "7". На значениях 500 и 800 ошибка бы не проявилась — и тем она опаснее.