15 Aralık 2021

Element size and scrolling

There are many JavaScript properties that allow us to read information about element width, height and other geometry features.

We often need them when moving or positioning elements in JavaScript, to correctly calculate coordinates.

Sample element

As a sample element to demonstrate properties we’ll use the one given below:

<div id="example">
  ...Text...
</div>
<style>
  #example {
    width: 300px;
    height: 200px;
    border: 25px solid #E8C48F;
    padding: 20px;
    overflow: auto;
  }
</style>

It has the border, padding and scrolling. The full set of features. There are no margins, as they are not the part of the element itself, and there are no special properties for them.

The element looks like this:

You can open the document in the sandbox.

Mind the scrollbar

The picture above demonstrates the most complex case when the element has a scrollbar. Some browsers (not all) reserve the space for it by taking it from the content.

So, without scrollbar the content width would be 300px, but if the scrollbar is 16px wide (the width may vary between devices and browsers) then only 300 - 16 = 284px remains, and we should take it into account. That’s why examples from this chapter assume that there’s a scrollbar. If there’s no scrollbar, then things are just a bit simpler.

The padding-bottom may be filled with text

Usually paddings are shown empty on illustrations, but if there’s a lot of text in the element and it overflows, then browsers show the “overflowing” text at padding-bottom, so you can see that in examples. But the padding is still there, unless specified otherwise.

Geometry

Element properties that provide width, height and other geometry are always numbers. They are assumed to be in pixels.

Here’s the overall picture:

They are many properties, it’s difficult to fit them all in the single picture, but their values are simple and easy to understand.

Let’s start exploring them from the outside of the element.

offsetParent, offsetLeft/Top

These properties are rarely needed, but still they are the “most outer” geometry properties, so we’ll start with them.

The offsetParent is the nearest ancestor that is:

  1. CSS-positioned (position is absolute, relative, fixed or sticky),
  2. or <td>, <th>, <table>,
  3. or <body>.

In most practical cases we can use offsetParent to get the nearest CSS-positioned ancestor. And offsetLeft/offsetTop provide x/y coordinates relative to its upper-left corner.

In the example below the inner <div> has <main> as offsetParent and offsetLeft/offsetTop shifts from its upper-left corner (180):

<main style="position: relative" id="main">
  <article>
    <div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
  </article>
</main>
<script>
  alert(example.offsetParent.id); // main
  alert(example.offsetLeft); // 180 (note: a number, not a string "180px")
  alert(example.offsetTop); // 180
</script>

There are several occasions when offsetParent is null:

  1. For not shown elements (display:none or not in the document).
  2. For <body> and <html>.
  3. For elements with position:fixed.

offsetWidth/Height

Now let’s move on to the element itself.

These two properties are the simplest ones. They provide the “outer” width/height of the element. Or, in other words, its full size including borders.

For our sample element:

  • offsetWidth = 390 – the outer width, can be calculated as inner CSS-width (300px) plus paddings (2 * 20px) and borders (2 * 25px).
  • offsetHeight = 290 – the outer height.
Geometry properties for not shown elements are zero/null

Geometry properties are calculated only for shown elements.

If an element (or any of its ancestors) has display:none or is not in the document, then all geometry properties are zero or null depending on what it is.

For example, offsetParent is null, and offsetWidth, offsetHeight are 0.

We can use this to check if an element is hidden, like this:

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

Please note that such isHidden returns true for elements that are on-screen, but have zero sizes (like an empty <div>).

clientTop/Left

Inside the element we have the borders.

To measure them, there are properties clientTop and clientLeft.

In our example:

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

…But to be precise – they are not borders, but relative coordinates of the inner side from the outer side.

What’s the difference?

It becomes visible when the document is right-to-left (the operating system is in Arabic or Hebrew languages). The scrollbar is then not on the right, but on the left, and then clientLeft also includes the scrollbar width.

In that case, clientLeft would be not 25, but with the scrollbar width 25 + 16 = 41:

clientWidth/Height

These properties provide the size of the area inside the element borders.

They include the content width together with paddings, but without the scrollbar:

On the picture above let’s first consider clientHeight: it’s easier to evaluate. There’s no horizontal scrollbar, so it’s exactly the sum of what’s inside the borders: CSS-height 200px plus top and bottom paddings (2 * 20px) total 240px.

Now clientWidth – here the content width is not 300px, but 284px, because 16px are occupied by the scrollbar. So the sum is 284px plus left and right paddings, total 324px.

If there are no paddings, then clientWidth/Height is exactly the content area, inside the borders and the scrollbar (if any).

So when there’s no padding we can use clientWidth/clientHeight to get the content area size.

scrollWidth/Height

  • Properties clientWidth/clientHeight only account for the visible part of the element.
  • Properties scrollWidth/scrollHeight also include the scrolled out (hidden) parts:

On the picture above:

  • scrollHeight = 723 – is the full inner height of the content area including the scrolled out parts.
  • scrollWidth = 324 – is the full inner width, here we have no horizontal scroll, so it equals clientWidth.

We can use these properties to expand the element wide to its full width/height.

Like this:

// expand the element to the full content height
element.style.height = `${element.scrollHeight}px`;

Click the button to expand the element:

text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text

scrollLeft/scrollTop

Properties scrollLeft/scrollTop are the width/height of the hidden, scrolled out part of the element.

On the picture below we can see scrollHeight and scrollTop for a block with a vertical scroll.

In other words, scrollTop is “how much is scrolled up”.

scrollLeft/scrollTop can be modified

Most of the geometry properties here are read-only, but scrollLeft/scrollTop can be changed, and the browser will scroll the element.

If you click the element below, the code elem.scrollTop += 10 executes. That makes the element content scroll 10px down.

Click
Me
1
2
3
4
5
6
7
8
9

Setting scrollTop to 0 or Infinity will make the element scroll to the very top/bottom respectively.

Don’t take width/height from CSS

We’ve just covered geometry properties of DOM elements. They are normally used to get widths, heights and calculate distances.

But as we know from the chapter Styles and classes, we can read CSS-height and width using getComputedStyle.

So why not to read the width of an element like this?

let elem = document.body;

alert( getComputedStyle(elem).width ); // show CSS width for elem

Why should we use geometry properties instead? There are two reasons:

  1. First, CSS width/height depend on another property: box-sizing that defines “what is” CSS width and height. A change in box-sizing for CSS purposes may break such JavaScript.

  2. Second, CSS width/height may be auto, for instance for an inline element:

    <span id="elem">Hello!</span>
    
    <script>
      alert( getComputedStyle(elem).width ); // auto
    </script>

    From the CSS standpoint, width:auto is perfectly normal, but in JavaScript we need an exact size in px that we can use in calculations. So here CSS width is useless at all.

And there’s one more reason: a scrollbar. Sometimes the code that works fine without a scrollbar starts to bug with it, because a scrollbar takes the space from the content in some browsers. So the real width available for the content is less than CSS width. And clientWidth/clientHeight take that into account.

…But with getComputedStyle(elem).width the situation is different. Some browsers (e.g. Chrome) return the real inner width, minus the scrollbar, and some of them (e.g. Firefox) – CSS width (ignore the scrollbar). Such cross-browser differences is the reason not to use getComputedStyle, but rather rely on geometry properties.

If your browser reserves the space for a scrollbar (most browsers for Windows do), then you can test it below.

The element with text has CSS width:300px.

On a Desktop Windows OS, Firefox, Chrome, Edge all reserve the space for the scrollbar. But Firefox shows 300px, while Chrome and Edge show less. That’s because Firefox returns the CSS width and other browsers return the “real” width.

Please note that the described difference is only about reading getComputedStyle(...).width from JavaScript, visually everything is correct.

Summary

Elements have the following geometry properties:

  • offsetParent – is the nearest positioned ancestor or td, th, table, body.
  • offsetLeft/offsetTop – coordinates relative to the upper-left edge of offsetParent.
  • offsetWidth/offsetHeight – “outer” width/height of an element including borders.
  • clientLeft/clientTop – the distance from the upper-left outer corner to its upper-left inner corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so clientLeft includes its width too.
  • clientWidth/clientHeight – the width/height of the content including paddings, but without the scrollbar.
  • scrollWidth/scrollHeight – the width/height of the content including the scrolled out parts. Also includes paddings, but not the scrollbar.
  • scrollLeft/scrollTop – width/height of the scrolled out part of the element, starting from its upper-left corner.

All properties are read-only except scrollLeft/scrollTop. They make the browser scroll the element if changed.

Görevler

önem: 5

The elem.scrollTop property is the size of the scrolled out part from the top. How to get “scrollBottom” – the size from the bottom?

Write the code that works for an arbitrary elem.

P.S. Please check your code: if there’s no scroll or the element is fully scrolled down, then it should return 0.

The solution is:

let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;

In other words: (full height) minus (scrolled out top part) minus (visible part) – that’s exactly the scrolled out bottom part.

önem: 3

Write the code that returns the width of a standard scrollbar.

For Windows it usually varies between 12px and 20px. If the browser doesn’t reserves any space for it, then it may be 0px.

P.S. The code should work for any HTML document, do not depend on its content.

To get the scrollbar width, we can create an element with the scroll, but without borders and paddings.

Then the difference between its full width offsetWidth and the inner content area width clientWidth will be exactly the scrollbar:

// create a div with the scroll
let div = document.createElement('div');

div.style.overflowY = 'scroll';
div.style.width = '50px';
div.style.height = '50px';

// must put it in the document, otherwise sizes will be 0
document.body.append(div);
let scrollWidth = div.offsetWidth - div.clientWidth;

div.remove();

alert(scrollWidth);
önem: 5

Here’s how the source document looks:

What are coordinates of the field center?

Calculate them and use to place the ball into the center of the field:

  • The element should be moved by JavaScript, not CSS.
  • The code should work with any ball size (10, 20, 30 pixels) and any field size, not be bound to the given values.

P.S. Sure, centering could be done with CSS, but here we want exactly JavaScript. Further we’ll meet other topics and more complex situations when JavaScript must be used. Here we do a “warm-up”.

Görevler için korunaklı alan aç.

The ball has position:absolute. It means that its left/top coordinates are measured from the nearest positioned element, that is #field (because it has position:relative).

The coordinates start from the inner left-upper corner of the field:

The inner field width/height is clientWidth/clientHeight. So the field center has coordinates (clientWidth/2, clientHeight/2).

…But if we set ball.style.left/top to such values, then not the ball as a whole, but the left-upper edge of the ball would be in the center:

ball.style.left = Math.round(field.clientWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2) + 'px';

Here’s how it looks:

To align the ball center with the center of the field, we should move the ball to the half of its width to the left and to the half of its height to the top:

ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';

Attention: the pitfall!

The code won’t work reliably while <img> has no width/height:

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

When the browser does not know the width/height of an image (from tag attributes or CSS), then it assumes them to equal 0 until the image finishes loading.

In real life after the first load browser usually caches the image, and on next loads it will have the size immediately.

But on the first load the value of ball.offsetWidth is 0. That leads to wrong coordinates.

We should fix that by adding width/height to <img>:

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

…Or provide the size in CSS:

#ball {
  width: 40px;
  height: 40px;
}

Çözümü korunaklı alanda aç.

önem: 5

What’s the difference between getComputedStyle(elem).width and elem.clientWidth?

Give at least 3 differences. The more the better.

Differences:

  1. clientWidth is numeric, while getComputedStyle(elem).width returns a string with px at the end.
  2. getComputedStyle may return non-numeric width like "auto" for an inline element.
  3. clientWidth is the inner content area of the element plus paddings, while CSS width (with standard box-sizing) is the inner content area without paddings.
  4. If there’s a scrollbar and the browser reserves the space for it, some browser substract that space from CSS width (cause it’s not available for content any more), and some do not. The clientWidth property is always the same: scrollbar size is substracted if reserved.
Eğitim haritası

Yorumlar

yorum yapmadan önce lütfen okuyun...
  • Eğer geliştirme ile alakalı bir öneriniz var ise yorum yerine github konusu gönderiniz.
  • Eğer makalede bir yeri anlamadıysanız lütfen belirtiniz.
  • Koda birkaç satır eklemek için <code> kullanınız, birkaç satır eklemek için ise <pre> kullanın. Eğer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)