Metrics

  1. Prerequisite: CSS box model
  2. Example document
  3. Box metrics
  4. Practice
  5. Summary

It is possible to position elements with JavaScript. A simple example of such positioning is a tooltip which follows mouse.

This section describes how to get/calculate coordinates of elements and their position.

Prerequisite: CSS box model

The CSS box model is painted below:

It is described in the CSS Box model specification.
Knowing it’s components is a preliminary knowledge to going any further.

Example document

The example document is at tutorial/browser/dom/metric.html.

Before you continue reading, it would be a good idea to open it.

We’ll use the following box in demonstrations:

<div id="example">
  
## Introduction   

  The contents. 
</div>

The box is positioned absolutely, has borders, paddings, margins, and so scrollbar:

#example {
  position: absolute;

  width: 300px;
  height: 200px;

  left: 160px;
  top: 160px;

  padding: 20px;
  margin: 20px;

  overflow: auto;
  border: 25px solid #F0E68C;
}

The CSS picture:

Box metrics

CSS width/height
Size of the content area, which lies inside the padding. CSS properties can be set using element.style property and retrieved using getComputedStyle()/currentStyle. Read more in the article Styles and classes, getComputedStyle.

Next we’ll learn more about other times of width and height available in JavaScript.

All JavaScript metrics are in pixels and don’t have 'px' at the end.

clientWidth/Height

Size of the client area: content area with paddings, but without scrollbars.

The sizes can be calculated as:

clientWidth = 300(width) + 40(paddings) - 16(scrollbar) = 324
clientHeight = 200(height) + 40(paddings) = 240

If there is no padding, and the box is scrollable, clientWidth/Height show the real content area size:

On the picture above, CSS width is with the scrollbar. You can’t actually insert something of 300px in the box. The real available width is clientWidth.

scrollWidth/Height
Content area width and height including the scrolled out part.
  • scrollHeight = 723 - full height with scrollable area
  • scrollWidth = 324 - full width with scrollable area

scrollWidth/Height is same as clientWidth/Height, but includes full scrollable area.

The following code changes the vertical size of an element to show all contents:

element.style.height = element.scrollHeight+'px'

scrollTop/scrollLeft
Size of scrolled out part: vertical and horizontal. The value is always in pixels.

The picture below illustrates scrollHeight and scrollTop for a vertically scrollable box.

scrollLeft/scrollTop are writeable

Unlike other properties, which are read-only, you can change scrollLeft/scrollTop, and the browser scrolls the element.

In standards mode, the scroll of the document is in document.documentElement. The following code scrolls the document 10px down:

offsetWidth/Height
Outer box width/height, full size with borders, but without margins.
  • offsetWidth = 390 - outer box width
  • offsetHeight = 290 - outer box height

This is how the box looks from outside.

clientTop/Left
The indent of client area from box outer corner.

In other words, the width of top/left border in pixels.

  • clientLeft = 25 - left border width
  • clientTop = 25 - top border width

There are two exceptions to the general border-width meaning:

  1. In case of a right-to-left document (arabic, hebrew), the clientLeft property also includes the width of a right scrollbar.
  2. In IE<8 and IE8 compat. mode: document.documentElement (or document.body if in quirksmode) is shifted a bit from left-upper corner of the document. There is no border, but document.body.clientLeft/clientTop is not zero (usually 2) in this case.
offsetParent, offsetLeft/Top
Properties offsetLeft and offsetTop reflect a relative shift of an element from its offsetParent.

The offsetParent is the parent element in the sense of layout. For example, if an element is positioned absolutely, the offsetParent is not it’s DOM parent, but a nearest positioned element (or BODY).

The full rule for offsetParent:

  • For static positioning - the nearest table cell or BODY (in standards mode).
  • For other types of positioning - a closest positioned element.

Metrics for invisible elements are zero.

JavaScript coordinates and sizes are set for attached and displayed elements only.

They equal 0 for elements with display:none or out of DOM. The offsetParent is also null for such elements.

We could use this to check if an elem is hidden:

function isHidden(elem)
  return !elem.offsetWidth && !elem.offsetHeight
}

  • Works even if parent element has display:none.
  • Works for all elements except TR, on which it bugs in some browsers. But usually we check other elements than TR, so it’s ok.
  • Doesn’t work if the element has visibility:hidden or is positioned off-screen. Empty elements will also be hidden.

Practice

There was a green-bordered div in the text:

A programmer John of your team wrote the code to shift the div to right-top with position: absolute:

var div = document.getElementById('moving-div')
div.style.position = 'absolute'
div.style.right = div.style.top = 0

Naturally, the text after DIV shifted up:

Enhance the code, make the text keep it’s place even after the DIV is shifted.

Hint: Create a helper DIV with the same size as the green-bordered DIV and insert it in the document. This is called making a placeholder.

Should be like this (placeholder got background for demo purposes):

The source document: tutorial/browser/dom/replaceDiv/2.html.

P.S… Do it without any additional CSS.

Open solution
Solution

What we need is to create a div with same height and insert it instead of the moving one.

var div = document.getElementById('moving-div')

var placeHolder = document.createElement('div')
placeHolder.style.height = div.offsetHeight + 'px'

  • offsetHeight is outer box height includig borders, but margins are not counted.
  • 'px' is required for CSS property.

But we’re not done yet. The placeHolder doesn’t have a margin, so the text will shift.

Let’s use JavaScript to copy the margin:

var div = document.getElementById('moving-div')

var placeHolder = document.createElement('div')
placeHolder.style.height = div.offsetHeight + 'px'

// IE || other browser
var computedStyle = div.currentStyle ||  getComputedStyle(div, null)

placeHolder.style.marginTop = computedStyle.marginTop // full prop name
placeHolder.style.marginBottom = computedStyle.marginBottom

Full property name marginTop is used. It guarantees that for any combination of margin-top, margin-bottom, margin, the computed value is correct.

The final result (see 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>


Place a ball in the center of the field.

Source:

Use JavaScript to place the ball in the center:

The source document: tutorial/browser/dom/ball-source/index.html.

Open solution
Solution

The field has no padding. So, it’s width and height are clientWidth/Height.

The center is (clientWidth/2, clientHeight/2).

If we set ball.style.left/top of the ball to the center, then the left-upper corner of the ball will be at the center, not the ball itself:

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'

Click to see the current result

To align the center of the ball against the field center, we need to shift the ball. Half of it’s width left, half of it’s height up.

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'

Unfortunately, there will be a bug, because IMG has no width/height:

<img src="ball.gif" id="ball">

Width/height of an image is unknown to the browser until it loads, if not set explicitly. So, we’ll have ball.offsetWidth = 0.

A reasonable fix is to provide width/height:

<img src="ball.gif" width="40" height="40" id="ball">

Now we’re done.

Click to see the final result

Full solution code: tutorial/browser/dom/ball/index.html

P.S. Using offsetHeight/offsetWidth instead of clientHeight/clientWidth would be wrong, because positioning takes place inside the borders.

Summary

There are following properties:

  • clientWidth/clientHeight - width/height of the visible in-border area (can be called a client area.
    The client area includes padding and doesn’t include scrollbars.
  • clientLeft/clientTop - left/top border width or, more generally, a shift of the client area from the top-left corner of the box.
    Also used in IE, because document.body may be shifted there.
  • scrollWidth/scrollHeight - width/height of the scrollable in-border area. Includes padding. Doesn’t include scrollbars.
  • scrollLeft/scrollTop - the width/height of the scrolled out part of the document, starting from the top-left corner.
  • offsetWidth/offsetHeight - the “outer” width/height of the box as seen from outside, excluding margins.
  • offsetParent - the nearest table-cell, body for static positioning or the nearest positioned element for other positioning types.
  • offsetLeft/offsetTop - the position in pixels of top-left corner of the box related to it’s offsetParent.

The summarizing picture for all properties except scrolls:

Tutorial

Donate

Donate to this project