This commit is contained in:
Ilya Kantor 2017-02-28 12:54:48 +03:00
parent 4272b7bb13
commit 508969c13f
168 changed files with 340 additions and 10 deletions

View file

@ -0,0 +1,5 @@
The solution is:
```js
let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;
```

View file

@ -0,0 +1,11 @@
importance: 5
---
# What's the scroll from the bottom?
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`.

View file

@ -0,0 +1,20 @@
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:
```js run
// 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);
```

View file

@ -0,0 +1,11 @@
importance: 3
---
# What is the scrollbar width?
Write the code that returns the width of a standard scrollbar.
For Windows it usually varies between `12px` and `20px`, but if the browser reserves no space for it, then it may be `0px`.
P.S. The code should work in any HTML document, do not depend on its content.

View file

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#field {
width: 200px;
border: 10px groove black;
background-color: #00FF00;
position: relative;
}
#ball {
position: absolute;
}
</style>
</head>
<body>
<div id="field">
<img src="https://js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<script>
var ball = document.getElementById('ball')
var field = document.getElementById('field')
// ball.offsetWidth=0 before image loaded!
// to fix: set width
ball.style.left = Math.round(field.clientWidth / 2) + 'px'
ball.style.top = Math.round(field.clientHeight / 2) + 'px'
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,54 @@
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:
![](field.png)
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:
```js
ball.style.left = Math.round(field.clientWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2) + 'px';
```
Here's how it looks:
[iframe height=180 src="ball-half"]
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:
```js
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:
```html
<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>`:
```html
<img src="ball.png" *!*width="40" height="40"*/!* id="ball">
```
...Or provide the size in CSS:
```css
#ball {
width: 40px;
height: 40px;
}
```

View file

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#field {
width: 200px;
border: 10px groove black;
background-color: #00FF00;
position: relative;
}
#ball {
position: absolute;
}
</style>
</head>
<body>
<div id="field">
<img src="https://en.js.cx/clipart/ball.svg" width="40" height="40" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
<script>
// ball.offsetWidth=0 before image loaded!
// to fix: set width
ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px'
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px'
</script>
</body>
</html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE HTML>
<html>
<head>
<style>
#field {
width: 200px;
border: 10px groove black;
background-color: #00FF00;
position: relative;
}
#ball {
position: absolute;
}
</style>
</head>
<body>
<div id="field">
<img src="https://en.js.cx/clipart/ball.svg" id="ball"> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
</div>
</body>
</html>

View file

@ -0,0 +1,20 @@
importance: 5
---
# Place the ball in the field center
Here's how the source document looks:
[iframe src="source" edit link height=180]
What are coordinates of the field center?
Calculate them and use to place the ball into the center of the field:
[iframe src="solution" height=180]
- 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".

View file

@ -0,0 +1,6 @@
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 conand sometent 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.

View file

@ -0,0 +1,9 @@
importance: 5
---
# The difference: CSS width versus clientWidth
What's the difference between `getComputedStyle(elem).width` and `elem.clientWidth`?
Give at least 3 differences. The more the better.

View file

@ -0,0 +1,274 @@
# Element size and scrolling
To show elements at arbitrary places in the page we should:
1. First, know CSS positioning.
2. Second, know how to handle "geometry" properties in Javascript.
[cut]
## Sample element
For the example we'll use the element with the border, padding and scrolling:
```html no-beautify
<div id="example">
...Text...
</div>
<style>
#example {
width: 300px;
height: 200px;
border: 25px solid #E8C48F;
padding: 20px;
overflow: auto;
}
</style>
```
It has no margins, because they are irrelevant here for us, as they are not the part of the element itself.
The element looks like this:
![](metric-css.png)
You can [open the document in the sandbox](sandbox:metric).
```smart header="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 for devices and browsers) then only `300-16 = 284px` remains. Our code should work well if the scrollbar exists and occupies some place, so we consider it the case here.
```
```smart header="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 the browsers show it at `padding-bottom`.
```
## 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:
![](metric-all.png)
All properties hardly fit in the picture, but as we'll see soon, their values are simple and easy to understand.
Let's start exploring them from the outer side 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` or `fixed`).
2. or `<td>`, `<th>`, `<table>`.
2. or `<body>`
The `offsetParent` alone has no use. But `offsetLeft/offsetTop` provide x/y coordinates relative to it's left-upper corner.
In the example below the inner `<div>` has `<main>` as `offsetParent` and `offsetLeft/offsetTop` are shifts from its left-upper corner (`180`):
```html run height=10
<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>
```
![](metric-offset-parent.png)
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` on them.
## offsetWidth/Height
Now let's move 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.
![](metric-offset-width-height.png)
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.
````smart header="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 `offsetParent` is `null` and `offsetWidth`, `offsetHeight` and other numeric properties are `0`.
We can use this to check if an element is hidden, like this:
```js
function isHidden(elem) {
return !elem.offsetWidth && !elem.offsetHeight;
}
```
Should keep in mind 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
![](metric-client-left-top.png)
...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 (OS 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` in our example would be not `25`, but with the scrollbar width `25+16=41`:
![](metric-client-left-top-rtl.png)
## 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:
![](metric-client-width-height.png)
On the picture above let's first consider `clientHeight`: it's easier to evaluate. There's no horizontal scrollbar, so its exactly the sum of what's inside the borders: CSS-высота `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 scrollbbar. 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).**
![](metric-client-width-nopadding.png)
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` add the scrolled out (hidden) part:
![](metric-scroll-width-height.png)
On the picture above:
- `scrollHeight = 723` -- is the full inner height of the content area including the scrolled out part.
- `scrollWidth = 324` -- is the full inner width, here we have no horizontal scroll, so it equals `clientWidth`.
We can use these properties to open the element wide to its full width/height, by the code:
```js
element.style.height = element.scrollHeight + 'px';
```
```online
Click the button to open wide the element:
<div id="element" style="width:300px;height:200px; padding: 0;overflow: auto; border:1px solid black;">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</div>
<button style="padding:0" onclick="element.style.height = element.scrollHeight + 'px'">element.style.height = element.scrollHeight + 'px'</button>
```
## scrollLeft/scrollTop
Properties `scrollLeft/scrollTop` show how much is hidden behind the scroll. It's 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:
![](metric-scroll-top.png)
````smart header="`scrollLeft/scrollTop` can be modified"
Unlike most other geometry properties that are read-only, `scrollLeft/scrollTop` can be changed, and the browser will scroll the element.
```online
If you click the element below, the code `elem.scrollTop += 10` executes. That makes the element content scroll `10px` below.
<div onclick="this.scrollTop+=10" style="cursor:pointer;border:1px solid black;width:100px;height:80px;overflow:auto">Click<br>Me<br>1<br>2<br>3<br>4<br>5<br>6<br>7<br>8<br>9</div>
```
Setting `scrollTop` to `0` or `Infinity` will make the element scroll to the 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 distances.
Now let's see what we should not use.
As we know from the chapter <info:styles-and-classes>, we can read CSS-height and width using `getComputedStyle`.
So we can try to read the width of an element like this:
```js run
let elem = document.body;
alert( getComputedStyle(elem).width ); // show CSS width for elem
```
Why we should use geometry properties instead?
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 purposes of CSS may break such JavaScript.
2. Second, CSS `width/height` may be `auto`, for instance for an inline element:
```html run
<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 is the reason of many problems. The devil is in the detail. Sometimes the code that works fine without a scrollbar starts to bug with it.
As we've seen 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 some browsers also take that into account in `getComputedStyle(elem).width`. That is: some of them return real inner width and some of them -- CSS width. Such cross-browser differences is a reason not to use `getComputedStyle`, but rather rely on geometry propeties.
```online
If your browser reserves the space for a scrollbar (most browsers for Windows do), then you can test it below.
[iframe src="cssWidthScroll" link border=1]
The element with text has CSS `width:300px`.
Desktop Windows 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 are 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 left-upper edge of `offsetParent`.
- `offsetWidth/offsetHeight` -- "outer" width/height of an element including borders.
- `clientLeft/clientTop` -- the distance from the left-upper outer corner to its left-upper 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 part. Also includes paddings, but not the scrollbar.
- `scrollLeft/scrollTop` -- width/height of the scrolled out part of the element, starting from its left-upper corner.
All properties are read-only except `scrollLeft/scrollTop`. They make the browser scroll the element if changed.

View file

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<body>
<div id="elem" style="overflow-y:scroll;width:300px;height:200px;border:1px solid black">
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
</div>
The element has <code>style="width:300px"</code>
<br>
<button onclick="alert( getComputedStyle(elem).width )">alert( getComputedStyle(elem).width )</button>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 233 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

View file

@ -0,0 +1,89 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
* {
margin: 0;
padding: 0;
}
#example {
width: 300px;
height: 200px;
overflow: auto;
border: 25px solid #E8C48F;
padding: 20px;
}
.key {
cursor: pointer;
text-decoration: underline;
}
</style>
</head>
<body>
<div id="example">
<h3>Introduction</h3>
<p>This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company's Navigator 2.0 browser.
It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the
Ecma General Assembly of June 1997.</p>
<p>That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep
it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.</p>
<p>The third edition of the Standard introduced powerful regular expressions, better string handling, new control statements, try/catch exception handling, tighter definition of errors, formatting for numeric output and minor changes in anticipation
of forthcoming internationalisation facilities and future language growth. The third edition of the ECMAScript standard was adopted by the Ecma General Assembly of December 1999 and published as ISO/IEC 16262:2002 in June 2002.</p>
</div>
<div id="mouse-wrap">Mouse coordinates: <span id="mouse">...</span></div>
<div id="info"></div>
<script>
let props = {
geometry: ['clientLeft', 'clientTop', 'clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight', 'scrollWidth', 'scrollHeight'],
scroll: ['scrollLeft', 'scrollTop'],
offsetParent: ['offsetParent', 'offsetLeft', 'offsetTop']
};
info.innerHTML = '<h3>Click to see the value:</h3>';
for (let k in props) {
info.innerHTML += `<h4>${k}</h4>`;
let prop = props[k];
for (let i = 0; i < prop.length; i++) {
info.innerHTML += "<span class='key'>" + prop[i] + '</span>: <span id="' + prop[i] + '">&nbsp;</span>' + " "
i++;
if (i < prop.length) {
info.innerHTML += "<span class='key'>" + prop[i] + '</span>: <span id="' + prop[i] + '">&nbsp;</span>';
}
info.innerHTML += "<br/>";
}
}
document.onclick = function(event) {
let target = event.target;
if (!target.classList.contains('key')) return;
let prop = target.innerHTML;
let value = example[prop];
value = value.tagName || value;
document.getElementById(prop).innerHTML = value;
};
document.onmousemove = function(e) {
document.getElementById('mouse').innerHTML = Math.round(e.clientX) + ':' + Math.round(e.clientY);
};
</script>
</body>
</html>