getComputedStyle: какой стиль на самом деле
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
У элемента может не быть ни одного собственного стиля — и при этом он зелёный, с отступом в десять пикселей и шрифтом от родителя. element.style об этом промолчит: он показывает только атрибут style. Спросить браузер, что получилось на самом деле, умеет getComputedStyle.
Заданный стиль разбросан по многим местам: внешний файл, тег <style>, атрибут style, стили самого браузера. Вычисленный существует в единственном экземпляре — один набор значений на элемент, итог каскада, специфичности, наследования и значений по умолчанию. Именно он определяет, как элемент выглядит.
getComputedStyle(element) возвращает этот итог: объект, у которого любое CSS-свойство спрашивают по camelCase-имени — color, fontSize, paddingTop.
getComputedStyle — метод window, а не элемента. element.getComputedStyle() не существует, хотя рука тянется написать именно так.
element.style — не «стиль элемента», а разобранный атрибут style. Нет атрибута — нет и значений, сколько бы правил ни оформляло элемент. Это не баг и не недоделка: объект честно отражает ровно одну строчку разметки.
Зато писать в него удобно: свойство сразу попадает в атрибут и получает в каскаде вес выше любого селектора (кроме !important). Обратная сторона — читать оттуда можно только то, что вы туда сами положили.
Вычисленный стиль — уже посчитанный. em и rem превращены в пиксели, ключевые слова приведены к каноническому виду, цвета — к rgb() или rgba(). Браузеру так удобнее, и вам обычно тоже: не нужно самому знать размер шрифта родителя.
Цена — потеря исходной записи. Строка "10px" не помнит, что в CSS стояло 0.5em, а "rgb(255, 0, 0)" не помнит слова red. Вычисленный стиль годится, чтобы узнать, но не чтобы «вернуть как было».
То, что отдаёт getComputedStyle, спецификация называет resolved value. Для большинства свойств это вычисленное значение, а для размеров и координат — использованное: то, что реально получилось после раскладки. Поэтому width: 50% вернётся пикселями.
Отсюда неожиданность: у элемента с display: none раскладки нет, считать нечего — и вместо пикселей приходит объявленное значение, чаще всего auto.
| Что спрашиваем | Что приходит |
|---|---|
| width, height | пиксели после раскладки — если элемент отрисован |
| left, top | пиксели у позиционированных элементов |
| fontSize, lineHeight | пиксели, проценты и em уже пересчитаны; lineHeight без явного значения — слово normal, а не число |
| color, backgroundColor | rgb(...) или rgba(...) |
| display, position | каноническое ключевое слово: none, block, flex |
| margin, background и другие сокращения | по-разному в разных браузерах — спрашивайте marginTop, backgroundColor |
Псевдоэлементов нет в DOM: textContent их не видит, querySelector не находит. Но стиль у них есть, и вторым аргументом getComputedStyle принимает строку с псевдоэлементом.
CSS-переменные живут отдельно от обычных свойств: поля accent у объекта нет, читать нужно через getPropertyValue("--accent"). Значение приходит почти как написано в CSS — вплоть до пробела в начале, поэтому привыкайте к .trim().
Первое ограничение — объект только для чтения. Попытка записать в него не «тихо не сработает», а бросит исключение. Менять стиль по-прежнему через element.style или переключение классов.
Второе — объект живой. Это не снимок, а окно в текущее состояние: сохранили его в переменную, поменяли элемент — и то же самое поле отдаёт уже новое значение. Удобно, но означает, что каждое чтение может стоить браузеру пересчёта.
Чтение вычисленного стиля сразу после изменения DOM — это принудительный пересчёт: браузер обязан отдать актуальное значение, значит должен применить все ваши правки прямо сейчас, не дожидаясь отрисовки. В цикле получаются десятки пересчётов вместо одного.
Лечится разделением: сначала все чтения, потом все записи. Ещё дешевле — не читать вовсе, а держать нужное состояние в 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 в ней — сцена, элемент, в который пример кладёт свою разметку:
Размеры и координаты прибор берёт у 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.
Все три причины видны в вычисленном стиле — а откуда они пришли, класс это или инлайн, уже неважно:
Прозрачность приходит строкой ("0"), поэтому сравниваем через Number. Проверка честная, но не всесильная: у потомка скрытого родителя display останется своим ("block"), хотя на экране его нет. Для такого случая есть element.checkVisibility() и element.offsetParent === null.
Жирный ли шрифт
Насыщенность шрифта задают то словом, то числом. Напишите isBold(element), который отвечает true при насыщенности 700 и выше — независимо от формы записи в CSS.
Вычисленный стиль уже привёл bold к числу — но отдал его строкой, так что сравнивать нужно числа:
Сравнивать строки нельзя: "1000" >= "700" даёт false, потому что строки сравниваются посимвольно и "1" меньше "7". На значениях 500 и 800 ошибка бы не проявилась — и тем она опаснее.