Размеры и скроллинг элементов в JavaScript

В этой статье познакомимся с тем, как в JavaScript получить текущие метрики элемента, включая его ширину и высоту.
Блочная модель CSS
Блочная модель в CSS – это набор правил, на основании которых выполняется расчёт размеров элемента на странице. Эту модель необходимо знать, чтобы понимать, какие размеры будет иметь тот или иной блочный элемент на странице:

Здесь:
content
– содержимое блока (в этом примере его ширинаwidth
равна800px
, а высотаheight
–150px
);padding
– внутренний отступ;border
– рамка;margin
– внешний отступ.
Кроме этого, элемент может иметь ещё outline
(внешнюю границу). Но она не занимает места, не влияет на его размеры и положение.
По умолчанию, когда мы в CSS элементу устанавливаем width
и height
, они применяются к content
:
.box {
width: 800px;
height: 150px;
padding: 15px 20px;
border: 3px solid #696a6f;
margin: 20px 8px;
}
В этом сценарии «внешняя» ширина блока будет 846px
(800 + 20 * 2 + 3 * 2
), а высота – 186px
(150 + 15 * 2 + 3 * 2
).
Но в CSS кроме такого поведения (box-sizing: content-box
) имеется ещё border-box
:
.box {
box-sizing: border-box;
width: 846px;
height: 186px;
padding: 15px 20px;
border: 3px solid #696a6f;
margin: 20px 8px;
}

При такой настройке мы с помощью width
и height
уже устанавливаем элементу внешнюю ширину и высоту. А размеры его content
области будут уже вычисляться исходя из этих значений.
Кроме этого, размер доступной области content
зависит ещё зависит от полос прокрутки. Если полоса прокрутки в браузере на данном устройстве занимает место, то для её отображения будет отбираться соответствующее место у контента.
Размеры элемента
Блочная модель CSS довольно сложна, особенно, когда элемент имеет полосы прокрутки. Поэтому, в JavaScript для описания размеров элемента на странице имеется такое многообразие DOM-свойств:
offsetWidth
иoffsetHeight
– «внешняя» ширина и высота элемента (в пикселях), включая рамку;clientLeft
иclientTop
– толщина левой и верхней границы элемента (если направление текста справа налево, тоclientLeft
включает в себя ещё ширину вертикальной полосы прокрутки);clientWidth
иclientHeight
– ширина и высотаcontent
, включаяpadding
(без прокрутки);scrollWidth
иscrollHeight
– ширина и высотаcontent
, включаяpadding
и содержимое, которое не помещается на экран и требует прокрутки.

Перед тем как рассмотреть пример, отметим, что при разработке сайтов очень часто сразу для всех элементов устанавливают border-box
:
*,
*::before,
*::after {
box-sizing: border-box;
}
Поэтому, изучать размеры элементов будем с учётом этой настройки:
<style>
.box-wrapper {
width: 300px;
height: 400px;
padding: 15px;
margin: 20px;
overflow: scroll;
}
.box-1 {
height: 100px;
margin-bottom: 15px;
}
.box-2 {
height: 150px;
width: 450px;
margin-bottom: 15px;
}
.box-3 {
height: 450px;
}
</style>
<div class="box-wrapper">
<div class="box-1"></div>
<div class="box-2"></div>
<div class="box-3"></div>
</div>
На рисунке в правом верхнем углу показаны значения DOM-свойств для .box-wrapper
:

1. Свойства offsetWidth
и offsetHeight
возвращают «внешние» размеры элемента.

То есть размеры, которые получаются сложением content
,padding
, ширины соответствующей полосы прокрутки (если она отображается и занимает место) и рамки. Для .box-wrapper
:
offsetWidth
равно CSS-ширине или300px
;offsetHeight
равно CSS-высотеheight
или400px
.
Свойства offsetWidth
и offsetHeight
возвращают целые значения. Но фактические размеры элемента могут быть дробными. Поэтому, если нужно узнать точные размеры, то можно воспользоваться getBoundingClientRect
:
// получаем элемент .box-wrapper
const boxWrapperElem = document.querySelector('.box-wrapper');
// целые «внешние» размеры
const width1 = boxWrapperElem.offsetWidth;
const height1 = boxWrapperElem.offsetHeight;
// точные «внешние» размеры
const boxWrapperClientRect = boxWrapper.getBoundingClientRect();
const width2 = boxWrapperClientRec.width;
const height2 = boxWrapperClientRec.height;
2. Свойства clientLeft
и clientTop
возвращают соответственно толщину левой и верхней границ. Но, clientLeft
также включает ширину вертикальной полосы прокрутки, если направление текста установлено справа налево (поскольку в этом случае полоса отображается слева).

В этом примере:
clientLeft
равен15px
(толщине левой рамки);clientTop
возвращает толщину верхней рамки, которая равна15px
.
3. Свойства clientWidth
и clientHeight
возвращают размеры области содержимого элемента, включая внутренние отступы (padding
), но без учета границ (border
) и прокрутки (scroll
).

Свойство clientWidth
равно 253px
и оно на этом устройстве вычислилось следующим образом:
300px
(offsetWidth
);-15px
(clientLeft
или толщина левой рамки);-15px
(толщина правой рамки.box-wrapper
);-17px
(ширина прокрутки на этом устройстве).
Свойство clientHeight
равно 353px
и оно на этом устройстве вычислилось следующим образом:
400px
(offsetHeight
);-15px
(clientTop
или толщина верхней рамки);-15px
(толщина нижней рамки);-17px
(ширина горизонтальной прокрутки на этом устройстве).
4. Свойства scrollWidth
и scrollHeight
возвращают размеры области содержимого элемента, включая внутренние отступы (padding
) и содержимое, которое не помещается на экране и требует прокрутки.

Свойство scrollWidth
аналогично clientWidth
, но учитывает контент .box-wrapper
, который не видно. В этом примере scrollWidth
равна 480px
:
15px
(padding-left
блока.box-wrapper
);450px
(ширина блока.box-2
, так как из-за него и возникает прокрутка);15px
(padding-right
блока.box-wrapper
).
Величина scrollHeight
равна 760px
. Её можно посчитать следующим образом:
15px
(padding-top
блока.box-wrapper
);100px
(внешняя высота.box-1
);15px
(margin-bottom
, установленный для.box-1
);150px
(внешняя высота.box-2
);15px
(margin-bottom
, установленный для.box-2
);450px
(внешняя высота.box-3
);15px
(padding-bottom
блока.box-wrapper
).
Положение элемента
Кроме размеров элемента, нам также очень важно знать как он расположен на странице. В JavaScript мы можем это получить с помощью DOM-свойств offsetLeft
и offsetTop
.
Очень важно, эти свойства показывают положение элемента (его смещение) относительно offsetParent
.
Например, определим позицию элемента #child
на странице относительно его offsetParent
(в данном примере им является #parent
):
<div id="parent" style="position: relative; padding: 10px 15px;">
<div id="child">
</div>
<script>
const child = document.querySelector('#child');
console.log(childElem.offsetParent.id); // "parent"
console.log(childElem.offsetLeft); // 15
console.log(childElem.offsetTop); // 10
</script>

Теперь рассмотрим, что такое offsetParent
.
offsetParent
– это свойство элемента, с помощью которого мы можем получить ближайшего предка с position
отличным от static
. Другими словами, оно позволяет найти предка относительного которого данный элемент позиционируется.
Поиск предка всегда начинается с родителя, дальше осуществляется переход к родителю его родителя и так далее. Если на очередном шаге элемент отвечает искомому, то он возвращается в качестве результата. В противном случае document.body
.
Например, найдём offsetParent
для элемента, который помещён в блок, у которого position
равен absolute
:
<div id="parent" style="position: absolute;">
<div id="child"></div>
</div>
<script>
const childElem = document.querySelector('#child');
const offsetParentElem = childElem.offsetParent;
// найдём ближайший родительский элемент с position отличным от static
console.log(offsetParentElem);
</script>
Кроме этого offsetParent
рассматривает в качестве цели не только элементы с position
отличным от static
, но и <table>
, <td>
и <th>
.
Пример использования:
const tdElem = document.querySelector('#myTd');
const tdOffsetParentElem = tdElem.offsetParent;
// выведем ближайший родительский элемент таблицы с position отличным от static
console.log(tdOffsetParentElem);
const tableElem = document.querySelector('#myTable');
const tableOffsetParentElem = tableElem.offsetParent;
// выведем ближайший родительский элемент с position отличным от static, который может быть как внутри таблицы, так и за ее пределами
console.log(tableOffsetParentElem);
При этом предком может быть только:
- позиционированный элемент, т.е. такой, который имеет
position
отличное отstatic
, т.е.relative
,absolute
,fixed
илиsticky
; <td>
,<th>
или<table>
;<body>
.
offsetParent
возвращает null
в следующих ситуациях:
- для
<body>
; - элемент не отображается (он или его предок имеет
display:none
) или его нет в документе (он создан, но не вставлен); - элемент имеет фиксированное позиционирование
position:fixed
;
Прокрутка элемента
Положение прокрутки определяется с помощью свойств scrollLeft
и scrollTop
. Они показывают количество пикселей, на которое прокручено содержимое элемента по горизонтали и вертикали.
Свойства scrollLeft
и scrollTop
в отличие от других доступны не только для чтения, но и для изменения.
scrollLeft и scrollTop - это свойства объекта элемента в JavaScript, которые возвращают текущую позицию прокрутки элемента по горизонтали и вертикали соответственно. Они могут использоваться для определения текущей позиции прокрутки элемента и для управления прокруткой программно. Например, чтобы прокрутить элемент вверх или вниз на определенное количество пикселей, можно установить значение свойства scrollTop равным нужному количеству пикселей.
В отличие от большинства свойств, которые доступны только для чтения, значения scrollLeft и scrollTop можно изменять, и браузер выполнит прокрутку элемента.
<div class="box-wrapper">
...
</div>
<script>
// получим элемент .box-wrapper
const boxWrapperElem = document.querySelector('.box-wrapper');
// выведем в консоль позицию горизонтальной прокрутки
console.log(boxWrapperElem.scrollLeft);
// выведем в консоль позицию вертикальной прокрутки
console.log(boxWrapperElem.scrollTop);
// установим в качестве прокрутки по x значение 100px
boxWrapperElem.scrollLeft = 100;
// прокрутим по у на 50px
boxWrapperElem.scrollTop += 50;
</script>

Разное
Для задания элементу ширины и высоты следует использовать стили:
<div id="box"></div>
<script>
const $box = document.querySelector('#box');
// устанавливаем ширину $box
$box.style.width = '100px';
// устанавливаем высоту $box
$box.style.height = '100px';
</script>
Задачи
1. Устранить сдвиг при открытии модального окна
При открытии модального окна мы добавляем к body
свойство overflow:hidden
.
При этом, если у нас отображается вертикальная полоса прокрутки, которая занимает место, то при обрезании контента происходит смещение контента.
Эту ситуацию необходимо исправить.
Подсказки:
- необходимо вычислить ширину прокрутки (т.к. в разных браузерах и на разных устройствах она может иметь разную ширину);
- при открытии модального окна следует добавить к body правый margin со значением которое будет компенсировать ширину прокрутки.
Комментарии: 1
Примеры в статье и в песочнице различаются.
Отсюда если не заходить в песочницу кажется ничего не понятным....В песочнице данные точнее (новее).
Например: -15px (толщина правой рамки .box-wrapper); но в статье нет рамки CSSЧтобы попасть в песочницу нажимайте кнопку запустить в примере