3. Свойства offsetWidth, offsetLeft, offsetHeight, offsetTop и offsetParent


В JavaScript у каждого DOM-элемента есть набор свойств, позволяющих получить информацию о его геометрических размерах и положении относительно родительского контейнера. Эти свойства часто используются для динамического позиционирования элементов, создания анимаций, проверки видимости и других задач, связанных с вёрсткой. Рассмотрим каждое из них подробно.

1. offsetParent

Свойство offsetParent возвращает ссылку на ближайший предок элемента, который является позиционированным (имеет CSS-свойство position отличное от static), или, если такового нет, возвращает body или html (в зависимости от браузера). Также существуют особые случаи, когда offsetParent может быть null (например, для элементов с display: none, для body или html в некоторых браузерах).

Как работает:

  • Браузер проходит вверх по дереву DOM от текущего элемента и ищет первого предка, у которого position не равно static (relative, absolute, fixed, sticky).
  • Если такой предок найден, он становится offsetParent.
  • Если нет, то offsetParent обычно равен body или html. Однако для элементов с фиксированным позиционированием (position: fixed) offsetParent может быть null (поскольку они позиционируются относительно окна просмотра, а не другого элемента).
  • Исключения: для элементов td, th, table в качестве offsetParent может выступать сама таблица, даже если она не позиционирована.
  • Важно: если элемент скрыт через display: none, то offsetParent равен null.

2. offsetLeft и offsetTop

Свойства offsetLeft и offsetTop возвращают расстояние в пикселях от верхнего левого угла внешней границы (border-box) текущего элемента до внутренней границы (border-box) его offsetParent. Измерение включает:

  • Отступы (padding) и границы (border) родителя? Нет, они не учитываются – расстояние считается от внешнего края родительского элемента до внешнего края текущего.
  • Значения округлены до целых чисел.

Пример:

Если у родителя есть border толщиной 5px, то offsetLeft будет равен расстоянию от границы родителя до границы элемента, то есть включать border родителя в это расстояние? На самом деле нет: точка отсчёта – левый верхний угол рамки родителя (включая его border). То есть если родитель имеет border, то его содержимое начинается после границы, но координаты элемента считаются от внешнего края рамки родителя. Это может быть неочевидно, поэтому нужно тестировать.

Фактически: offsetLeft/offsetTop дают смещение элемента относительно внешнего края offsetParent. То есть включают border родителя, но не включают margin текущего элемента (так как margin – это внешнее пространство элемента, и он влияет на положение, но не входит в размеры box). Координаты считаются от левого верхнего угла рамки offsetParent до левого верхнего угла рамки текущего элемента.

3. offsetWidth и offsetHeight

offsetWidth и offsetHeight возвращают полные размеры элемента, включая:

  • Ширину/высоту содержимого (content width/height)
  • Внутренние отступы (padding)
  • Границы (border)
  • Но не включают внешние отступы (margin). Это так называемая border-box ширина/высота.

Отличие от других свойств:

  • clientWidth / clientHeight – возвращают размеры внутренней области (включая padding, но без border и без полос прокрутки, если они есть).
  • scrollWidth / scrollHeight – возвращают полный размер прокручиваемого содержимого (включая невидимые области), также без border, но с padding.
  • Когда элемент скрыт (display: none), все эти свойства равны 0.

6. Нюансы и особенности

Округление: все значения возвращаются целыми числами (пиксели округлены). Если нужна высокая точность (например, для трансформаций), можно использовать getBoundingClientRect().

Скрытые элементы: если элемент или его предок скрыт через display: none, то offsetParent равен null, а offsetLeft, offsetTop, offsetWidth, offsetHeight равны 0. Однако если элемент скрыт через visibility: hidden, он занимает место, и свойства будут корректными.

Изменение размеров/положения: чтение этих свойств может вызвать принудительный перерасчёт стилей и макета (reflow), что может сказаться на производительности при частом использовании в циклах. Рекомендуется кэшировать значения, если они используются многократно.

Сложные случаи: при наличии трансформаций (transform, scale, rotate) значения offsetLeft/offsetTop/offsetWidth/offsetHeight всё равно возвращают размеры и положение до применения трансформаций (т.е. в координатах исходного потока). Это может ввести в заблуждение – для учёта трансформаций лучше использовать getBoundingClientRect().

Для элемента body: offsetParent обычно null. А offsetWidth/offsetHeight дают размеры видимой области окна? Не совсем – это размеры элемента body с учётом его padding/border, но не окна. Для окна используют window.innerWidth/innerHeight или document.documentElement.clientWidth/clientHeight.