В этом разделе мы поговорим о размерах элементов DOM, способах их вычисления и метриках — различных свойствах, которые содержат эту информацию.
Образец документа
Образец документа: tutorial/browser/dom/metric/metric.html.
Перед тем, как продолжать чтение, ознакомьтесь с ним.
В примерах мы будем использовать блок с рамкой (border), полями (padding), отступами (margin) и прокруткой:
<div id="example">
...Текст...
</div>
<style>
#example {
width: 300px;
height: 200px;
border: 25px solid #F0E68C; /* рамка 25px */
padding: 20px; /* поля 20px */
margin: 20px; /* отступы 20px */
overflow: auto; /* прокрутка */
}
</style>
Результат выглядит так:

Вы можете открыть его по этой ссылке.
CSS-метрики width/height
CSS-высоту и ширину width/height можно установить с помощью elem.style и извлечь, используя getComputedStyle()/currentStyle:
style = window.getComputedStyle ? getComputedStyle(elem, '') : elem.currentStyle; alert(style.width); // вывести CSS-ширину
Они относятся к размеру внутренней части элемента, которая лежит внутри полей padding.
На рисунке выше нижнее поле заполнено прокруткой, но оно всё равно подразумевается и не входит в высоту height.
Подробнее об этих функциях — в статье Стили и классы, getComputedStyle.
Полоса прокрутки
Полоса прокрутки — причина многих проблем и недопониманий. Поэтому мы будем разбирать её влияние на метрики самым внимательным образом.
При наличии вертикальной полосы прокрутки — она забирает себе часть ширины элемента.
Ширина полосы прокрутки обычно составляет 16px за редким исключением (мобильные и редкие браузеры, настройка прокрутки при помощи специализированных CSS-свойств), поэтому внутренняя область в примере будет уже не 300px, а 284px.
То есть, несмотря на то, что на рисунке полоса прокрутки находится визуально в правом поле — отнимает место она не у поля, а у внутренней ширины.
…Но при этом некоторые браузеры отражают это уменьшение ширины в результате getComputedStyle(...).width, а некоторые — нет:
На рисунке ниже в CSS указано width:300px. А вот getComputedStyle возвращает 300px/284px, в зависимости от браузера:

Вы можете увидеть значение getComputedStyle(...).width, нажав на кнопку в ифрейме:
Еще раз заметим, что разночтения касаются только чтения свойства getComputedStyle(...).width из JavaScript, CSS тут не при чем. Отображение будет одинаковым. Ширина текста при наличии прокрутки уменьшается до 284px.
Здесь и далее, мы будем понимать под width именно реальную ширину внутренней области (284px), а не результат чтения CSS-свойства width, который может быть разным.
JavaScript-метрики
В JavaScript существует ряд дополнительных свойств, содержащих размеры элементов.
Метрики JavaScript, в отличие от свойств CSS, содержат числа, в пикселях, без единиц измерения 'px' на конце.
clientWidth/Height-
Размер клиентской зоны — внутренняя область плюс поля.

Общая ширина внутри рамки - это
284 (width) + 20(padding left) + 20 (padding right) = 324.Получаем:
clientWidth = 284(width) + 2*20(padding) = 324 clientHeight = 200(height) + 2*20(padding) = 240
Обратите внимание, в
clientHeightвходят и верхнее и нижнее поля, несмотря на то, что нижнее поле заполнено текстом.Если полей нет, то
clientWidth/Heightпокажет реальный размер области данных, внутри рамок и полосы прокрутки.
Если поля есть — их необходимо будет вычесть.
scrollWidth/Height-
Ширина и высота контента с учетом прокручиваемой области.
scrollHeight = 723- полная высота, включая прокрученную областьscrollWidth = 324- полная ширина, включая прокрученную область
scrollWidth/Heightто же самое, что иclientWidth/Height, но включает в себя прокручиваемую область.
Эти свойства можно использовать, чтобы «распахнуть» элемент на всю ширину/высоту:
element.style.height = element.scrollHeight + 'px';
Нажмите на кнопку, чтобы распахнуть элемент:текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст текст scrollTop/scrollLeft- Размер невидимой прокрученной области: вертикальной и горизонтальной. Значение всегда измеряется в пикселях.
Следующее изображение иллюстрирует
scrollHeightиscrollTopдля блока с вертикальной прокруткой.
scrollLeft/scrollTopможно изменятьВ отличие от большинства свойств, которые доступны только для чтения, значения
scrollLeft/scrollTopможно изменить, и браузер выполнит прокрутку элемента.Следующий элемент имеет обработчик
onclick="this.scrollTop+=10". Он прокручивается по клику на 10px вниз:Кликни
Меня
1
2
3
4
5
6
7
8
9 offsetWidth/Height- Внешняя ширина/высота блока, полный размер, включая рамки, но без отступов(margin).
offsetWidth = 390- внешняя ширина блокаoffsetHeight = 290- внешняя высота блока

Эти свойства показывают внешние ширину и высоту блока, то как блок выглядит снаружи.
clientTop/Left- Отступ клиентской области от внешнего угла блока.
Другими словами, ширина верхней/левой рамки(border) в пикселях.
clientLeft = 25- ширина левой рамкиclientTop = 25- ширина верхней рамки

Казалось бы, зачем еще какие-то свойства, если ширину рамки можно получить напрямую из CSS? Обычно они действительно не нужны.
Но есть две ситуации, когда эти свойства бывают полезны:
- В случае, когда документ располагаетсясправа налево (арабский язык, иврит), свойство
clientLeftвключает в себя еще и ширину правой полосы прокрутки. - В IE<8 документ, а точнее — элемент
document.documentElementнемного смещен относительно верхнего левого угла документа. Несмотря на то, что рамки там нет, сдвиг существует и хранится вdocument.body.clientLeft/clientTop(обычно это 2 пикселя).
offsetParent,offsetLeft/TopoffsetParent— это родительский элемент в смысле отображения на странице.Например, если элемент позиционирован абсолютно, то таким родителем
offsetParentявляется ближайший позиционированный элемент (т.е. свойствоpositionне равноstatic), илиBODY, если таковой отсутствует.Свойства
offsetLeft/Topзадают смещение относительноoffsetParent.<div style="position: relative"> <div style="position: absolute; left: 180px; top: 180px">...</div> </div>

Координаты и размеры в JavaScript устанавливаются только для видимых элементов.
Они равны 0 для элементов с display:none или тех, которые вне DOM. Кстати, offsetParent для таких элементов тоже null.
Это дает нам замечательный способ для проверки, виден ли элемент:
function isHidden(elem) return !elem.offsetWidth && !elem.offsetHeight; }
- Работает, даже если родителю элемента установлено свойство
display:none. - Работает для всех элементов, кроме
TR, с которым возникают некоторые проблемы в разных браузерах. Обычно, проверяются неTR, поэтому всё ок
. - Считает элемент видимым, даже если позиционирован за пределами экрана или имеет свойство
visibility:hidden. - «Схлопнутый» элемент, например пустой
divбез высоты и ширины, будет считаться невидимым.
Итого
У элементов есть следующие метрики:
clientWidth/clientHeight- ширина/высота видимой области, включая поля, но не полосы прокрутки.clientLeft/clientTop- ширина левой/верхней рамки или, точнее, сдвиг клиентской области, относительно верхнего левого угла блока.
Также, используется в IE, т.к.document.bodyв нем может быть сдвинут.scrollWidth/scrollHeight- ширина/высота прокручиваемой области. Включат в себя поля и не включает полосы прокрутки.scrollLeft/scrollTop- ширина/высота прокрученной части документа, считается от верхнего левого угла.offsetWidth/offsetHeight- «внешняя» ширина/высота блока, не считая отступов.offsetParent- ближайшая ячейка таблицы, body для статического позиционирования или ближайший позиционированный элемент для других типов позиционирования.offsetLeft/offsetTop- позиция в пикселях левого верхнего угла блока, относительно егоoffsetParent.
Все свойства, кроме scrollLeft/scrollTop доступны только для чтения. Изменение этих свойств заставляет браузер прокручивать элемент.
Краткая схема:

Прокрутку элемента можно прочитать или изменить через свойства scrollLeft/Top.
В этой главе мы считали, что страница находится в режиме соответствия стандартам. В режиме совместимости — всё также, но некоторые старые браузеры требуют document.body вместо documentElement.
Напишите код, который возвращает ширину стандартной полосы прокрутки. Именно самой полосы, где ползунок. Обычно она равна 16px, но в редких и мобильных браузерах может колебаться от 14px до 18px.
Если для этого вам понадобится добавить в DOM вспомогательные элементы, то пусть они не будут видны на экране.
Алгоритм основан на том, что внутренний элемент по умолчанию занимает всю доступную ширину.
Поэтому если взять элемент и запихать в него внутренний, а потом добавить полосу прокрутки, то ширины внутреннего уменьшится. Размер, на который она уменьшилась — как раз и будет ширина полосы прокрутки.
Шаги:
- Добавить два
DIVодин в другом: внешнийdivи внутреннийinnerDiv, причем внутренний выше внешнего, но у внешнегоoverflow-y: hidden. - По умолчанию, внутренний распахнется на всю возможную ширину. Посчитаем её:
innerDiv.offsetWidth. - Добавим внешнему
DIVстильoverflow-y:scroll. При этом внутренний автоматически ужмется по ширине. - Пересчитаем ширину внутреннего.
- Разница (2) и (4) является ответом.
Чтобы внешний div не был виден, нельзя ему ставить display:none, в этом случае метрики не сработают. Как вариант — поставить position:absolute;visibility:hidden, чтобы он добавлялся, но был не виден.
Но если поставить position:absolute, то ширина такого div будет нулевая, потому что в нём нет текста, а элемент с таким позиционированием «подстраивается» по ширине под текст.
Так что дополнительно нужно будет поставить ему еще и ширину.
Код в результате:
var div = document.createElement('div');
div.style.cssText="position:absolute;height:50px;overflow-y:hidden;width:50px;visibility:hidden";
div.innerHTML = '<div style="height:100px"></div>';
var innerDiv = div.firstChild;
document.body.appendChild(div);
var w1 = innerDiv.offsetWidth;
div.style.overflowY = 'scroll';
var w2 = innerDiv.offsetWidth;
alert(w1 - w2);
В песочнице: tutorial/browser/dom/metric/scrollbarWidth.html
Посмотрим следующий случай из жизни. Был текст, который, в частности, содержал div с зелеными границами:
Программист Валера из вашей команды написал код, который позиционирует его абсолютно и смещает в правый верхний угол. Вот этот код:
var div = document.getElementById('moving-div')
div.style.position = 'absolute'
div.style.right = div.style.top = 0
Побочным результатом явилось смещение текста, который раньше шел после DIV. Теперь он поднялся вверх:
Допишите код Валеры, сделав так, чтобы текст оставался на своем месте после того, как DIV будет смещен.
Сделайте это путем создания вспомогательного DIV с теми же размерами, что и у желтого DIV. Используйте только JavaScript, без CSS.
Должно быть так (новому блоку задан фоновый цвет для демонстрации):
Исходный документ: tutorial/browser/dom/replaceDiv/2.html.
Нам нужно создать div с такими же размерами и вставить его на место «переезжающего».
var div = document.getElementById('moving-div')
var placeHolder = document.createElement('div')
placeHolder.style.height = div.offsetHeight + 'px'
offsetHeightвнешняя высота блока, включая границы, но отступы в ней не учитываются.'px'- требование CSS-свойства.
Но это еще не всё. У placeHolder нет отступов, поэтому текст сдвигается.
Что ж, давайте получим отступы:
var div = document.getElementById('moving-div')
var placeHolder = document.createElement('div')
placeHolder.style.height = div.offsetHeight + 'px'
// IE || другой браузер
var computedStyle = div.currentStyle || getComputedStyle(div, null)
placeHolder.style.marginTop = computedStyle.marginTop // полное название свойства
placeHolder.style.marginBottom = computedStyle.marginBottom
Использование полного название свойства(marginTop) гарантирует, что полученное значение будет корректным.
Конечный результат (смотри SCRIPT):
<!DOCTYPE HTML>
<html>
<head>
<style>
#moving-div {
border: 5px groove green;
padding: 5px;
margin: 10px;
background-color: yellow;
}
</style>
</head>
<body>
Before Before Before
<div id="moving-div">
Text Text Text<br>
Text Text Text<br>
</div>
After After After
<script>
var div = document.getElementById('moving-div')
var placeHolder = document.createElement('div')
placeHolder.style.height = div.offsetHeight + 'px'
var computedStyle = div.currentStyle || getComputedStyle(div, null)
placeHolder.style.marginTop = computedStyle.marginTop // full prop name
placeHolder.style.marginBottom = computedStyle.marginBottom
// highlight it for demo purposes
placeHolder.style.backgroundColor = '#C0C0C0'
document.body.insertBefore(placeHolder, div)
div.style.position = 'absolute'
div.style.right = div.style.top = 0
</script>
</body>
</html>
Поместите мяч в центр поля.
Исходный код:
Используйте JavaScript, чтобы поместить мяч в центр:
Код должен определять размер мяча динамически, используя DOM.
Исходный документ: tutorial/browser/dom/ball-source/index.html.
Общая схема решения: позиционируем мяч абсолютно внутри поля.
При таком позиционировании координаты отсчитываются от внутреннего угла поля, например верхнего-левого:

Метрики для внутренней зоны поля — это clientWidth/Height.
Центр - это (clientWidth/2, clientHeight/2).
Но если мы установим мячу такие значения ball.style.left/top, то в центре будет не сам мяч, а его левый верхний угол:
var ball = document.getElementById('ball');
var field = document.getElementById('field');
ball.style.left = Math.round(field.clientWidth / 2)+'px';
ball.style.top = Math.round(field.clientHeight / 2)+'px';
Для того, чтобы центр мяча находился в центре поля, на нужно сместить мяч на половину его ширины влево и на половину его высоты вверх.
var ball = document.getElementById('ball');
var field = document.getElementById('field');
ball.style.left = Math.round(field.clientWidth/2 - ball.offsetWidth/2)+'px';
ball.style.top = Math.round(field.clientHeight/2 - ball.offsetHeight/2)+'px';
К сожалению, здесь будет ошибка, потому что IMG идет без ширины/высоты:
<img src="ball.gif" id="ball">
Высота и ширина изображения неизвестны браузеру до тех пор, пока оно не загрузится, если только размер не указан в теге.
Поэтому, если изображение не в кеше браузера, то значение ball.offsetWidth равно 0, и вычислить координаты невозможно.
Чтобы это исправить, добавим width/height к картинке:
<img src="ball.gif" *!*width="40" height="40"*/!* id="ball">
Теперь браузер всегда знает ширину и высоту, так что все работает.
Полный код решения: tutorial/browser/dom/ball/index.html
В BODY есть элемент DIV с заданной шириной width.
Задача — написать код, который «распахнет» DIV по ширине на всю страницу.
Исходный документ (DIV — красный):
.
Код документа: tutorial/browser/dom/metric/bodywidth-src/index.html.
Расширить нужно точно по ширине, чтобы красный DIV не вылез за границы BODY.
P.S. Пользоваться следует исключительно средствами JS, при этом не подглядывая в стили. То есть, код должен быть универсален и не ломаться, если цифры в CSS станут другими.
P.P.S. После того, как решите… Будет ли ваше решение работать, если у красного DIV стоит position:absolute? Если нет, то почему и как его поправить?
Вначале рассмотрим неверный вариант.
Он выглядит так:
elem.style.width = '100%';
Если вы его попробуете, то увидите, что элемент начинает вылезать за рамки родителя.
Так происходит потому, что ширина — это то, что внутри padding. То есть, ставя ширину в 100%, вы говорите: «внутренняя область должна занимать 100% доступной ширины". А на padding остаётся 0%`. В результате поля вылезают наружу.
Правильное решение через clientWidth.
Доступную внутреннюю ширину родителя можно получить, вычитая padding из clientWidth, и присвоить элементу:
var bodyClientWidth = document.body.clientWidth; var style = window.getComputedStyle ? getComputedStyle(elem, '') : elem.currentStyle; *!* var bodyInnerWidth = bodyClientWidth - parseInt(style.paddingLeft) - parseInt(style.paddingRight); */!* elem.style.width = bodyInnerWidth + 'px';
Этот вариант сломается, если в IE<9 значение padding указано не в пикселях. Получение пикселей из процентов и других единиц измерения рассмотрено в главе Стили и классы, getComputedStyle.
Правильный вариант с CSS.
Самое лучшее решение получится, если вспомнить, что элемент и сам рад растянуться по всей доступной ширине, и делает это по умолчанию.
Достаточно вернуть ему стандартный алгоритм вычисленя ширины, установив width: auto:
elem.style.width = 'auto';
Но.. Это не будет работать для элементов, которые сами по себе не растягиваются, например в случае position: absolute или float.
Такой элемент можно расширить, используя предыдущее решение.
Документ с обоими решениями: tutorial/browser/dom/metric/bodywidth/index.html.
В чём отличия между getComputedStyle(elem, "").width и elem.clientWidth?
Приведите несколько.
Отличий как минимум несколько:
getComputedStyleне работает в IE<9.clientWidthсоответствует внутренней видимой области элемента, включаяpadding.При стандартном значении box-sizing
widthсоответствует зоне внутриpadding.- Если есть полоса прокрутки, то некоторые браузеры включают её ширину в
width, а некоторые — нет.Свойство
clientWidthполностью кросс-браузерно. Оно всегда обозначает размер за вычетом прокрутки, т.е. реально доступный для содержимого.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.