up
|
@ -250,12 +250,12 @@ For instance:
|
||||||
```smart header="Computed and resolved values"
|
```smart header="Computed and resolved values"
|
||||||
There are two concepts in [CSS](https://drafts.csswg.org/cssom/#resolved-values):
|
There are two concepts in [CSS](https://drafts.csswg.org/cssom/#resolved-values):
|
||||||
|
|
||||||
1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. If can look like `width:auto` or `font-size:125%`.
|
1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. If can look like `height:1em` or `font-size:125%`.
|
||||||
2. A *resolved* style value is the one finally applied to the element. Values like `auto` or `125%` are still abstract. The browser takes the computed value and makes all units fixed and absolute, for instance: `width:212px` or `font-size:16px`. For geometry properties resolved values may have a floating point, like `width:50.5px`.
|
2. A *resolved* style value is the one finally applied to the element. Values like `1em` or `125%` are relative. The browser takes the computed value and makes all units fixed and absolute, for instance: `height:20px` or `font-size:16px`. For geometry properties resolved values may have a floating point, like `width:50.5px`.
|
||||||
|
|
||||||
Long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient, and the standard changed.
|
Long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient, and the standard changed.
|
||||||
|
|
||||||
So nowadays `getComputedStyle` actually returns the final, resolved value in absolute units.
|
So nowadays `getComputedStyle` actually returns the resolved value of the property.
|
||||||
```
|
```
|
||||||
|
|
||||||
````warn header="`getComputedStyle` requires the full property name"
|
````warn header="`getComputedStyle` requires the full property name"
|
||||||
|
|
|
@ -3,3 +3,5 @@ The solution is:
|
||||||
```js
|
```js
|
||||||
let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;
|
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.
|
||||||
|
|
|
@ -6,6 +6,6 @@ importance: 3
|
||||||
|
|
||||||
Write the code that returns the width of a standard scrollbar.
|
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`.
|
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 in any HTML document, do not depend on its content.
|
P.S. The code should work for any HTML document, do not depend on its content.
|
||||||
|
|
|
@ -154,7 +154,7 @@ 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 its exactly the sum of what's inside the borders: CSS-height `200px` plus top and bottom paddings (`2*20px`) total `240px`.
|
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 scrollbbar. So the sum is `284px` plus left and right paddings, total `324px`.
|
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`.
|
||||||
|
|
||||||
|
@ -167,7 +167,7 @@ So when there's no padding we can use `clientWidth/clientHeight` to get the cont
|
||||||
## scrollWidth/Height
|
## scrollWidth/Height
|
||||||
|
|
||||||
- Properties `clientWidth/clientHeight` only account for the visible part of the element.
|
- Properties `clientWidth/clientHeight` only account for the visible part of the element.
|
||||||
- Properties `scrollWidth/scrollHeight` add the scrolled out (hidden) part:
|
- Properties `scrollWidth/scrollHeight` also include the scrolled out (hidden) part:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -176,14 +176,17 @@ On the picture above:
|
||||||
- `scrollHeight = 723` -- is the full inner height of the content area including the scrolled out part.
|
- `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`.
|
- `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:
|
We can use these properties to expand the element wide to its full width/height.
|
||||||
|
|
||||||
|
Like this:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// expand the element to the full content height
|
||||||
element.style.height = element.scrollHeight + 'px';
|
element.style.height = element.scrollHeight + 'px';
|
||||||
```
|
```
|
||||||
|
|
||||||
```online
|
```online
|
||||||
Click the button to open wide the element:
|
Click the button to expand 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>
|
<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>
|
||||||
|
|
||||||
|
@ -192,33 +195,33 @@ Click the button to open wide the element:
|
||||||
|
|
||||||
## scrollLeft/scrollTop
|
## 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.
|
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:
|
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".
|
||||||
|
|
||||||
````smart header="`scrollLeft/scrollTop` can be modified"
|
````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.
|
Most geometry properties that are read-only, but `scrollLeft/scrollTop` can be changed, and the browser will scroll the element.
|
||||||
|
|
||||||
```online
|
```online
|
||||||
If you click the element below, the code `elem.scrollTop += 10` executes. That makes the element content scroll `10px` below.
|
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>
|
<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.
|
Setting `scrollTop` to `0` or `Infinity` will make the element scroll to the very top/bottom respectively.
|
||||||
````
|
````
|
||||||
|
|
||||||
## Don't take width/height from CSS
|
## 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.
|
We've just covered geometry properties of DOM elements. They are normally used to get widths, heights and calculate distances.
|
||||||
|
|
||||||
Now let's see what we should not use.
|
But as we know from the chapter <info:styles-and-classes>, we can read CSS-height and width using `getComputedStyle`.
|
||||||
|
|
||||||
As we know from the chapter <info:styles-and-classes>, we can read CSS-height and width using `getComputedStyle`.
|
So why not to read the width of an element like this?
|
||||||
|
|
||||||
So we can try to read the width of an element like this:
|
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
let elem = document.body;
|
let elem = document.body;
|
||||||
|
@ -226,9 +229,9 @@ let elem = document.body;
|
||||||
alert( getComputedStyle(elem).width ); // show CSS width for elem
|
alert( getComputedStyle(elem).width ); // show CSS width for elem
|
||||||
```
|
```
|
||||||
|
|
||||||
Why we should use geometry properties instead?
|
Why we should 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 purposes of CSS may break such JavaScript.
|
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:
|
2. Second, CSS `width/height` may be `auto`, for instance for an inline element:
|
||||||
|
|
||||||
```html run
|
```html run
|
||||||
|
@ -243,11 +246,9 @@ Why we should use geometry properties instead?
|
||||||
|
|
||||||
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.
|
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.
|
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.
|
||||||
|
|
||||||
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 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 propeties.
|
||||||
|
|
||||||
...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
|
```online
|
||||||
If your browser reserves the space for a scrollbar (most browsers for Windows do), then you can test it below.
|
If your browser reserves the space for a scrollbar (most browsers for Windows do), then you can test it below.
|
||||||
|
@ -256,7 +257,7 @@ If your browser reserves the space for a scrollbar (most browsers for Windows do
|
||||||
|
|
||||||
The element with text has CSS `width:300px`.
|
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.
|
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 are only about reading `getComputedStyle(...).width` from JavaScript, visually everything is correct.
|
Please note that the described difference are only about reading `getComputedStyle(...).width` from JavaScript, visually everything is correct.
|
||||||
|
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 179 KiB |
|
@ -1,6 +1,6 @@
|
||||||
# Window sizes and scroll
|
# Window sizes and scroll
|
||||||
|
|
||||||
How to find out the browser window width? How to get the full height of the document, including the scrolled out part? How to scroll the page from JavaScript?
|
How to find out the width of the browser window? How to get the full height of the document, including the scrolled out part? How to scroll the page using JavaScript?
|
||||||
|
|
||||||
From the DOM point of view, the root document element is `document.documentElement`. That element corresponds to `<html>` and has geometry properties described in the [previous chapter](info:size-and-scroll). For some cases we can use it, but there are additional methods and pecularities important enough to consider.
|
From the DOM point of view, the root document element is `document.documentElement`. That element corresponds to `<html>` and has geometry properties described in the [previous chapter](info:size-and-scroll). For some cases we can use it, but there are additional methods and pecularities important enough to consider.
|
||||||
|
|
||||||
|
@ -13,39 +13,38 @@ Properties `clientWidth/clientHeight` of `document.documentElement` is exactly w
|
||||||

|

|
||||||
|
|
||||||
```online
|
```online
|
||||||
For instance, the button below shows the height of your window:
|
For instance, this button shows the height of your window:
|
||||||
|
|
||||||
<button onclick="alert(document.documentElement.clientHeight)">alert(document.documentElement.clientHeight)</button>
|
<button onclick="alert(document.documentElement.clientHeight)">alert(document.documentElement.clientHeight)</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
````warn header="Not `window.innerWidth/Height`"
|
````warn header="Not `window.innerWidth/Height`"
|
||||||
Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So what's the difference?
|
Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So what's the difference?
|
||||||
|
|
||||||
Properties `clientWidth/clientHeight`, if there's a scrollbar occupying some space, return the width/height inside it. In other words, they return width/height of the visible part of the document, available for the content.
|
If there's a scrollbar occupying some space, `clientWidth/clientHeight` provide the width/height inside it. In other words, they return width/height of the visible part of the document, available for the content.
|
||||||
|
|
||||||
And `window.innerWidth/innerHeight` ignore the scrollbar.
|
And `window.innerWidth/innerHeight` ignore the scrollbar.
|
||||||
|
|
||||||
If there's a scrollbar and it occupies some space, then these two lines show different values:
|
If there's a scrollbar, and it occupies some space, then these two lines show different values:
|
||||||
```js run
|
```js run
|
||||||
alert( window.innerWidth ); // full window width
|
alert( window.innerWidth ); // full window width
|
||||||
alert( document.documentElement.clientWidth ); // window width minus the scrollbar
|
alert( document.documentElement.clientWidth ); // window width minus the scrollbar
|
||||||
```
|
```
|
||||||
|
|
||||||
In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientWidth`.
|
In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientHeight/Width`.
|
||||||
````
|
````
|
||||||
|
|
||||||
```warn header="`DOCTYPE` is important"
|
```warn header="`DOCTYPE` is important"
|
||||||
Please note: top-level geometry properties may work a little bit differently when there's no `<!DOCTYPE HTML>` in HTML. Odd things are possible.
|
Please note: top-level geometry properties may work a little bit differently when there's no `<!DOCTYPE HTML>` in HTML. Odd things are possible.
|
||||||
|
|
||||||
In modern HTML we should always write it. Generally that's not a JavaScript question, but here it affects JavaScript as well.
|
In modern HTML we should always write `DOCTYPE`. Generally that's not a JavaScript question, but here it affects JavaScript as well.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Width/height of the document
|
## Width/height of the document
|
||||||
|
|
||||||
Theoretically, as the visibble part of the document is `documentElement.clientWidth/Height`, the full size should be `documentElement.scrollWidth/scrollHeight`.
|
Theoretically, as the root document element is `documentElement.clientWidth/Height`, and it encloses all the content, we could measure its full size as `documentElement.scrollWidth/scrollHeight`.
|
||||||
|
|
||||||
That's correct for regular elements.
|
These properties work well for regular elements. But for the whole page these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! For regular elements that's a nonsense.
|
||||||
|
|
||||||
But for the whole page these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! For regular elements that's a nonsense.
|
|
||||||
|
|
||||||
To have a reliable full window size, we should take the maximum of these properties:
|
To have a reliable full window size, we should take the maximum of these properties:
|
||||||
|
|
||||||
|
@ -63,11 +62,11 @@ Why so? Better don't ask. These inconsistencies come from ancient times, not a "
|
||||||
|
|
||||||
## Get the current scroll [#page-scroll]
|
## Get the current scroll [#page-scroll]
|
||||||
|
|
||||||
Regular elements have their current scroll state in `scrollLeft/scrollTop`.
|
Regular elements have their current scroll state in `elem.scrollLeft/scrollTop`.
|
||||||
|
|
||||||
What's with the page? Most browsers provide that for the whole page in `documentElement.scrollLeft/Top`, but Chrome/Safari/Opera have bugs (like [157855](https://code.google.com/p/chromium/issues/detail?id=157855), [106133](https://bugs.webkit.org/show_bug.cgi?id=106133)) and we should use `document.body` instead of `document.documentElement` there.
|
What's with the page? Most browsers provide `documentElement.scrollLeft/Top` for the document scroll, but Chrome/Safari/Opera have bugs (like [157855](https://code.google.com/p/chromium/issues/detail?id=157855), [106133](https://bugs.webkit.org/show_bug.cgi?id=106133)) and we should use `document.body` instead of `document.documentElement` there.
|
||||||
|
|
||||||
Luckily, we don't have to remember that at all, because of the special properties `window.pageXOffset/pageYOffset`:
|
Luckily, we don't have to remember these pecularities at all, because of the special properties `window.pageXOffset/pageYOffset`:
|
||||||
|
|
||||||
```js run
|
```js run
|
||||||
alert('Current scroll from the top: ' + window.pageYOffset);
|
alert('Current scroll from the top: ' + window.pageYOffset);
|
||||||
|
@ -79,7 +78,9 @@ These properties are read-only.
|
||||||
## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll]
|
## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll]
|
||||||
|
|
||||||
```warn
|
```warn
|
||||||
To scroll the page from JavaScript, its DOM must be fully loaded.
|
To scroll the page from JavaScript, its DOM must be fully built.
|
||||||
|
|
||||||
|
For instance, if we try to scroll the page from the script in `<head>`, it won't work.
|
||||||
```
|
```
|
||||||
|
|
||||||
Regular elements can be scrolled by changing `scrollTop/scrollLeft`.
|
Regular elements can be scrolled by changing `scrollTop/scrollLeft`.
|
||||||
|
@ -88,9 +89,7 @@ We can do the same for the page:
|
||||||
- For all browsers except Chrome/Safari/Opera: modify `document.documentElement.scrollTop/Left`.
|
- For all browsers except Chrome/Safari/Opera: modify `document.documentElement.scrollTop/Left`.
|
||||||
- In Chrome/Safari/Opera: use `document.body.scrollTop/Left` instead.
|
- In Chrome/Safari/Opera: use `document.body.scrollTop/Left` instead.
|
||||||
|
|
||||||
It should work.
|
It should work, but smells like cross-browser incompatibilities. Not good. Fortunately, there's a simpler, more universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
|
||||||
|
|
||||||
But there's a simpler, more universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo).
|
|
||||||
|
|
||||||
- The method `scrollBy(x,y)` scrolls the page relative to its current position. For instance, `scrollBy(0,10)` scrolls the page `10px` down.
|
- The method `scrollBy(x,y)` scrolls the page relative to its current position. For instance, `scrollBy(0,10)` scrolls the page `10px` down.
|
||||||
|
|
||||||
|
@ -107,16 +106,16 @@ But there's a simpler, more universal solution: special methods [window.scrollB
|
||||||
<button onclick="window.scrollTo(0,0)">window.scrollTo(0,0)</button>
|
<button onclick="window.scrollTo(0,0)">window.scrollTo(0,0)</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
These properties are cross-browser.
|
These methods work for all browsers the same way.
|
||||||
|
|
||||||
## scrollIntoView
|
## scrollIntoView
|
||||||
|
|
||||||
For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView).
|
For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView).
|
||||||
|
|
||||||
The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument `top`:
|
The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument:
|
||||||
|
|
||||||
- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window, upper side of the element aligned with the window top.
|
- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element is aligned with the window top.
|
||||||
- if `top=false`, then the element bottom is aligned with the window bottom.
|
- if `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element is aligned with the window bottom.
|
||||||
|
|
||||||
```online
|
```online
|
||||||
The button below scrolls the page to make itself show at the window top:
|
The button below scrolls the page to make itself show at the window top:
|
||||||
|
@ -130,7 +129,7 @@ And this button scrolls the page to show it at the bottom:
|
||||||
|
|
||||||
## Forbid the scrolling
|
## Forbid the scrolling
|
||||||
|
|
||||||
Sometimes we need to make the document "unscrollable". For instance, when we need to show a large message over it, and we want the visitor to interact with that message, not with the document.
|
Sometimes we need to make the document "unscrollable". For instance, when we need to cover it with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document.
|
||||||
|
|
||||||
To make the document unscrollable, its enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll.
|
To make the document unscrollable, its enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll.
|
||||||
|
|
||||||
|
@ -148,7 +147,7 @@ We can use the same technique to "freeze" the scroll for other elements, not jus
|
||||||
|
|
||||||
The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it.
|
The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it.
|
||||||
|
|
||||||
That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the content area became wider) then add `padding` to `document.body`.
|
That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width same.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
@ -168,8 +167,8 @@ Geometry:
|
||||||
Scrolling:
|
Scrolling:
|
||||||
|
|
||||||
- Read the current scroll: `window.pageYOffset/pageXOffset`.
|
- Read the current scroll: `window.pageYOffset/pageXOffset`.
|
||||||
- Scroll the page:
|
- Change the current scroll:
|
||||||
|
|
||||||
- `window.scrollTo(pageX,pageY)` -- absolute coordinates,
|
- `window.scrollTo(pageX,pageY)` -- absolute coordinates,
|
||||||
- `window.scrollBy(x,y)` -- scroll relative the current place?
|
- `window.scrollBy(x,y)` -- scroll relative the current place,
|
||||||
- `elem.scrollIntoView(top)` -- scroll to make `elem` visible.
|
- `elem.scrollIntoView(top)` -- scroll to make `elem` visible (align with the top/bottom of the window).
|
||||||
|
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 34 KiB |
|
@ -4,7 +4,7 @@ To move elements around we should be familiar with coordinates.
|
||||||
|
|
||||||
Most JavaScript methods deal with one of two coordinate systems:
|
Most JavaScript methods deal with one of two coordinate systems:
|
||||||
|
|
||||||
1. Relative to the window top/left.
|
1. Relative to the window(or another viewport) top/left.
|
||||||
2. Relative to the document top/left.
|
2. Relative to the document top/left.
|
||||||
|
|
||||||
It's important to understand the difference and which type is where.
|
It's important to understand the difference and which type is where.
|
||||||
|
@ -27,9 +27,9 @@ Like this:
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
Window coordinates do not take the scrolled out part of the document into account, they are calculated from the "window itself".
|
Window coordinates do not take the scrolled out part of the document into account, they are calculated from the window left-upper corner.
|
||||||
|
|
||||||
In other words, when we scroll the page, the element goes up or down and *its window coordinates change*. That's kind of important.
|
In other words, when we scroll the page, the element goes up or down, *its window coordinates change*. That's very important.
|
||||||
|
|
||||||
```online
|
```online
|
||||||
Click the button to see its window coordinates:
|
Click the button to see its window coordinates:
|
||||||
|
@ -48,12 +48,12 @@ If you scroll the page, the button position changes, and window coordinates as w
|
||||||
|
|
||||||
Also:
|
Also:
|
||||||
|
|
||||||
- Coordinates may be decimal fractions. That's normal, internally browser uses them for calculations. We don't have to round them when setting to `style.position.left/top` etc.
|
- Coordinates may be decimal fractions. That's normal, internally browser uses them for calculations. We don't have to round them when setting to `style.position.left/top`, the browser is fine with fractions.
|
||||||
- Coordinates may be negative. For instance, if the page is scrolled down and its top edge is above the window then `elem.getBoundingClientRect().top` is negative.
|
- Coordinates may be negative. For instance, if the page is scrolled down and the `elem` top is now above the window then `elem.getBoundingClientRect().top` is negative.
|
||||||
- Some browsers also add to the result `getBoundingClientRect` properties `width` and `height`. We could also get the same by the substraction: `height=bottom-top`, `width=right-left`.
|
- Some browsers (like Chrome) also add to the result `getBoundingClientRect` properties `width` and `height`. We can get them also by substraction: `height=bottom-top`, `width=right-left`.
|
||||||
|
|
||||||
```warn header="Coordinates right/bottom are different from CSS properties"
|
```warn header="Coordinates right/bottom are different from CSS properties"
|
||||||
If we compare window coordinates vs CSS positioning, then they are closest to `position:fixed` -- the position relative to the viewport.
|
If we compare window coordinates versus CSS positioning, then there are obvious similarities to `position:fixed` -- also the position relative to the viewport.
|
||||||
|
|
||||||
But in CSS the `right` property means the distance from the right edge, and the `bottom` -- from the bottom edge.
|
But in CSS the `right` property means the distance from the right edge, and the `bottom` -- from the bottom edge.
|
||||||
|
|
||||||
|
@ -102,9 +102,11 @@ elem.style.background = ''; // Error!
|
||||||
```
|
```
|
||||||
````
|
````
|
||||||
|
|
||||||
## Usage for position:fixed
|
## Using for position:fixed
|
||||||
|
|
||||||
Most of time we need coordinates to position something. In CSS to position an element relative to the viewport we use `position:fixed` together with the coorinates, usually `left/top`.
|
Most of time we need coordinates to position something. In CSS, to position an element relative to the viewport we use `position:fixed` together with `left/top` (or `right/bottom`).
|
||||||
|
|
||||||
|
We can use `getBoundingClientRect` to get coordinates of an element, and then to show something near it.
|
||||||
|
|
||||||
For instance, the function `createMessageUnder(elem, html)` below shows the message under `elem`:
|
For instance, the function `createMessageUnder(elem, html)` below shows the message under `elem`:
|
||||||
|
|
||||||
|
@ -143,21 +145,21 @@ Click the button to run it:
|
||||||
<button id="coords-show-mark">Button with id="coords-show-mark", the message will appear under it</button>
|
<button id="coords-show-mark">Button with id="coords-show-mark", the message will appear under it</button>
|
||||||
```
|
```
|
||||||
|
|
||||||
The code can be modified to show the message at the left, right, below, apply CSS animations to "fade it in" and so on.
|
The code can be modified to show the message at the left, right, below, apply CSS animations to "fade it in" and so on. That's easy, as we have all the coordinates and sizes of the element.
|
||||||
|
|
||||||
**But note the important detail: when the page is scrolled, the message flows away from the button.**
|
But note the important detail: when the page is scrolled, the message flows away from the button.
|
||||||
|
|
||||||
The reason is obvious: the message element uses `position: fixed`, so it remains at the same place while the page scrolls away.
|
The reason is obvious: the message element relies on `position:fixed`, so it remains at the same place of the window while the page scrolls away.
|
||||||
|
|
||||||
To change that, we need to use document-based coordinates. We'll cover them in the next chapter.
|
To change that, we need to use document-based coordinates and `position:absolute`.
|
||||||
|
|
||||||
## Document coordinates
|
## Document coordinates
|
||||||
|
|
||||||
Document-relative coordinates start from the left-upper corner of the document, not the window.
|
Document-relative coordinates start from the left-upper corner of the document, not the window.
|
||||||
|
|
||||||
In CSS, window coordinates correspond to `position:fixed`, while document coordinates are similar to `position:absolute` (out of other positioned elements, of course).
|
In CSS, window coordinates correspond to `position:fixed`, while document coordinates are similar to `position:absolute` on top.
|
||||||
|
|
||||||
We need document coordinates to stick something at a certain place of the document, so that it remains there during a page scroll.
|
We can use `position:absolute` and `top/left` to put something at a certain place of the document, so that it remains there during a page scroll. But we need the right coordinates first.
|
||||||
|
|
||||||
For clarity we'll call window coordinates `(clientX,clientY)` and document coordinates `(pageX,pageY)`.
|
For clarity we'll call window coordinates `(clientX,clientY)` and document coordinates `(pageX,pageY)`.
|
||||||
|
|
||||||
|
@ -167,25 +169,26 @@ When the page is not scrolled, then window coordinate and document coordinates a
|
||||||
|
|
||||||
And if we scroll it, then `(clientX,clientY)` change, because they are relative to the window, but `(pageX,pageY)` remain the same.
|
And if we scroll it, then `(clientX,clientY)` change, because they are relative to the window, but `(pageX,pageY)` remain the same.
|
||||||
|
|
||||||
Here's the same page after the vertical scroll.
|
Here's the same page after the vertical scroll:
|
||||||
|
|
||||||
- `clientY` becomes `0`, because the element is now on window top.
|
|
||||||
- `clientX` doesn't change, we didn't scroll horizontally.
|
|
||||||
- `pageX` and `pageY` remain the same, because they are relative to the *document*.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
- `clientY` of the header `"From today's featured article"` became `0`, because the element is now on window top.
|
||||||
|
- `clientX` didn't change, as we didn't scroll horizontally.
|
||||||
|
- `pageX` and `pageY` coordinates of the element are still the same, because they are relative to the document.
|
||||||
|
|
||||||
## Getting document coordinates [#getCoords]
|
## Getting document coordinates [#getCoords]
|
||||||
|
|
||||||
There's no standard method to get document coordinates. But it's easy to write it.
|
There's no standard method to get document coordinates of an element. But it's easy to write it.
|
||||||
|
|
||||||
The two coordinate systems are connected by the formula:
|
The two coordinate systems are connected by the formula:
|
||||||
- `pageY = clientY + current vertical scroll`.
|
- `pageY` = `clientY` + height of the scrolled-out vertical part of the document.
|
||||||
- `pageX = clientX + current horizontal scroll`.
|
- `pageX` = `clientX` + width of the scrolled-out horizontal part of the document.
|
||||||
|
|
||||||
Our function `getCoords(elem)` will take window coordinates from `elem.getBoundingClientRect()` and add the current scroll to them.
|
The function `getCoords(elem)` will take window coordinates from `elem.getBoundingClientRect()` and add the current scroll to them:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
// get document coordinates of the element
|
||||||
function getCoords(elem) {
|
function getCoords(elem) {
|
||||||
let box = elem.getBoundingClientRect();
|
let box = elem.getBoundingClientRect();
|
||||||
|
|
||||||
|
@ -201,8 +204,8 @@ function getCoords(elem) {
|
||||||
Any point on the page has coordinates:
|
Any point on the page has coordinates:
|
||||||
|
|
||||||
1. Relative to the window -- `elem.getBoundingClientRect()`.
|
1. Relative to the window -- `elem.getBoundingClientRect()`.
|
||||||
2. Relative to the document -- `elem.getBoundingClientRect()` plus the current scroll.
|
2. Relative to the document -- `elem.getBoundingClientRect()` plus the current page scroll.
|
||||||
|
|
||||||
Window coordinates are great to use with `position:fixed`, and document coordinates do well with `position:absolute`.
|
Window coordinates are great to use with `position:fixed`, and document coordinates do well with `position:absolute`.
|
||||||
|
|
||||||
Both coordinate systems have their "pro" and "contra", there are times we need one or the other one, just like CSS `position`.
|
Both coordinate systems have their "pro" and "contra", there are times we need one or the other one, just like CSS `position` `absolute` and `fixed`.
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
# Page loading: DOMContentLoaded, load, beforeunload, unload [todo: where put async defer scripts? in DOM?]
|
# Page lifecycle: DOMContentLoaded, load, beforeunload, unload
|
||||||
|
|
||||||
The process of loading an HTML document may be split into three stages:
|
The lifecycle of an HTML page has three important events:
|
||||||
|
|
||||||
- `DOMContentLoaded` -- the browser fully loaded HTML and built DOM.
|
- `DOMContentLoaded` -- the browser fully loaded HTML, and the DOM tree is built, but external resources like pictures `<img>` and stylesheets may be not yet loaded.
|
||||||
- `load` -- the browser loaded all resources (images, styles etc).
|
- `load` -- the browser loaded all resources (images, styles etc).
|
||||||
- `beforeunload/unload` -- leaving the page.
|
- `beforeunload/unload` -- when the user is leaving the page.
|
||||||
|
|
||||||
We can set a handler on every stage:
|
Each event may be useful:
|
||||||
|
|
||||||
- `DOMContentLoaded` event -- DOM is ready, we can lookup DOM nodes, initialize the interface. But images and styles may be not yet loaded.
|
- `DOMContentLoaded` event -- DOM is ready, so the handler can lookup DOM nodes, initialize the interface.
|
||||||
- `load` event -- the page and additional resources are loaded, it's rarely used, because usually we don't want to wait for that moment.
|
- `load` event -- additional resources are loaded, we can get image sizes (if not specified in HTML/CSS) etc.
|
||||||
- `beforeunload/unload` event -- we can check if the user saved changes he did in the page, ask him whether he's sure.
|
- `beforeunload/unload` event -- the user is leaving: we can check if the user saved the changes and ask him whether he really wants to leave.
|
||||||
|
|
||||||
Let's explore the details of these events.
|
Let's explore the details of these events.
|
||||||
|
|
||||||
|
@ -28,11 +28,13 @@ document.addEventListener("DOMContentLoaded", ready);
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
```html run height=150
|
```html run height=200 refresh
|
||||||
<script>
|
<script>
|
||||||
function ready() {
|
function ready() {
|
||||||
alert('DOM is ready');
|
alert('DOM is ready');
|
||||||
alert(`Image sizes: ${img.offsetWidth}x${img.offsetHeight}`);
|
|
||||||
|
// image is not yet loaded (unless was cached), so the size is 0x0
|
||||||
|
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
*!*
|
*!*
|
||||||
|
@ -40,7 +42,7 @@ For instance:
|
||||||
*/!*
|
*/!*
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img id="img" src="https://en.js.cx/clipart/hedgehog.jpg?speed=1&cache=0">
|
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
|
||||||
```
|
```
|
||||||
|
|
||||||
In the example the `DOMContentLoaded` handler runs when the document is loaded, not waits for the page load. So `alert` shows zero sizes.
|
In the example the `DOMContentLoaded` handler runs when the document is loaded, not waits for the page load. So `alert` shows zero sizes.
|
||||||
|
@ -49,168 +51,113 @@ At the first sight `DOMContentLoaded` event is very simple. The DOM tree is read
|
||||||
|
|
||||||
### DOMContentLoaded and scripts
|
### DOMContentLoaded and scripts
|
||||||
|
|
||||||
If there are `<script>...</script>` tags in the document, then the browser must execute them "at place" while building DOM.
|
When the browser initially loads HTML and comes across a `<script>...</script>` in the text, it can't continue building DOM. It must execute the script right now. So `DOMContentLoaded` may only happen after all such scripts are executed.
|
||||||
|
|
||||||
External scripts `<script src="...">` also put "DOM building" to pause while the script is loading and executing.
|
External scripts (with `src`) also put DOM building to pause while the script is loading and executing. So `DOMContentLoaded` waits for external scripts as well.
|
||||||
|
|
||||||
The exceptions are external scripts with `async` or `defer` attributes.
|
The only exception are external scripts with `async` and `defer` attributes. They tell the browser to continue processing without waiting for the scripts. So the user can see the page before scripts finish loading, good for performance.
|
||||||
|
|
||||||
- An async external script `<script async src="...">` is loaded and executed fully asynchronously, it doesn't pause anything.
|
```smart header="A word about `async` and `defer`"
|
||||||
- A deferred external script `<script defer src="...">` is loaded and executed fully asynchronously, it doesn't pause anything, with two differences from `async`:
|
Attributes `async` and `defer` work only for external scripts. They are ignored if there's no `src`.
|
||||||
1. If there are many external scripts with `defer`
|
|
||||||
2. lba
|
|
||||||
|
|
||||||
|
Both of them tell the browser that it may go on working with the page, and load the script "in background", then run the script when it loads. So the script doesn't block DOM building and page rendering.
|
||||||
|
|
||||||
|
There are two differences between them.
|
||||||
|
|
||||||
|
| | `async` | `defer` |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| Order | Scripts with `async` execute *in the load-first order*. Their document order doesn't matter -- which loads first runs first. | Scripts with `defer` always execute *in the document order* (as they go in the document). |
|
||||||
|
| `DOMContentLoaded` | Scripts with `async` may load and execute *before `DOMContentLoaded`*: if they are small, and the document is big, they load faster. | Scripts with `defer` always execute *after `DOMContentLoaded`*: they wait for it if needed. |
|
||||||
|
|
||||||
|
So `async` is used for totally independent scripts, and `defer` is used where we want some order.
|
||||||
|
|
||||||
Если в документе есть теги `<script>`, то браузер обязан их выполнить до того, как построит DOM. Поэтому событие `DOMContentLoaded` ждёт загрузки и выполнения таких скриптов.
|
```
|
||||||
|
|
||||||
Исключением являются скрипты с атрибутами `async` и `defer`, которые подгружаются асинхронно.
|
### DOMContentLoaded and styles
|
||||||
|
|
||||||
**Побочный эффект: если на странице подключается скрипт с внешнего ресурса (к примеру, реклама), и он тормозит, то событие `DOMContentLoaded` и связанные с ним действия могут сильно задержаться.**
|
External style sheets don't affect DOM, and so `DOMContentLoaded` does not wait for them.
|
||||||
|
|
||||||
Современные системы рекламы используют атрибут `async`, либо вставляют скрипты через DOM: `document.createElement('script')...`, что работает так же как `async`: такой скрипт выполняется полностью независимо от страницы и от других скриптов -- сам ничего не ждёт и ничего не блокирует.
|
But there's a pitfall: if we have a script after the style, then that script must wait for the stylesheet to execute:
|
||||||
|
|
||||||
### DOMContentLoaded и стили
|
|
||||||
|
|
||||||
Внешние стили никак не влияют на событие `DOMContentLoaded`. Но есть один нюанс.
|
|
||||||
|
|
||||||
**Если после стиля идёт скрипт, то этот скрипт обязан дождаться, пока стиль загрузится:**
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<link type="text/css" rel="stylesheet" href="style.css">
|
<link type="text/css" rel="stylesheet" href="style.css">
|
||||||
<script>
|
<script>
|
||||||
// сработает после загрузки style.css
|
// the script doesn't not execute until the stylesheet is loaded
|
||||||
|
alert(getComputedStyle(document.body).marginTop);
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
Такое поведение прописано в стандарте. Его причина -- скрипт может захотеть получить информацию со страницы, зависящую от стилей, например, ширину элемента, и поэтому обязан дождаться загрузки `style.css`.
|
The reason is that the script may want to get coordinates and other style-dependent properties of elements, like in the example above. Naturally, it has to wait for styles to load.
|
||||||
|
|
||||||
**Побочный эффект -- так как событие `DOMContentLoaded` будет ждать выполнения скрипта, то оно подождёт и загрузки стилей, которые идут перед `<script>`.**
|
As `DOMContentLoaded` waits for scripts, it now waits for styles before them as well.
|
||||||
|
|
||||||
### Автозаполнение
|
### Built-in browser autofill
|
||||||
|
|
||||||
Firefox/Chrome/Opera автозаполняют формы по `DOMContentLoaded`.
|
Firefox, Chrome and Opera autofill forms on `DOMContentLoaded`.
|
||||||
|
|
||||||
Это означает, что если на странице есть форма для ввода логина-пароля, то браузер введёт в неё запомненные значения только по `DOMContentLoaded`.
|
For instance, if the page has a form with login and password, and the browser remembered the values, then on `DOMContentLoaded` it may try to autofill them (if approved by the user).
|
||||||
|
|
||||||
**Побочный эффект: если `DOMContentLoaded` ожидает множества скриптов и стилей, то автозаполнение не сработает до полной их загрузки.**
|
So if `DOMContentLoaded` is postponed by long-loading scripts, then autofill also awaits. You probably saw that on some sites (if you use browser autofill) -- the login/password fields don't get autofilled immediately, but there's a delay till the page fully loads. That's actually the delay until the `DOMContentLoaded` event.
|
||||||
|
|
||||||
Конечно, это довод в пользу того, чтобы не задерживать `DOMContentLoaded`, в частности -- использовать у скриптов атрибуты `async` и `defer`.
|
One of minor benefits in using `async` and `defer` for external scripts -- they don't block `DOMContentLoaded` and don't delay browser autofill.
|
||||||
|
|
||||||
## window.onload [#window-onload]
|
## window.onload [#window-onload]
|
||||||
|
|
||||||
Событие `onload` на `window` срабатывает, когда загружается *вся* страница, включая ресурсы на ней -- стили, картинки, ифреймы и т.п.
|
The `load` event on the `window` object triggers when the whole page is loaded including styles, images and other resources.
|
||||||
|
|
||||||
Пример ниже выведет `alert` лишь после полной загрузки окна, включая `IFRAME` и картинку:
|
The example below correctly shows image sizes, because `window.onload` waits for all images:
|
||||||
|
|
||||||
```html run
|
```html run height=200 refresh
|
||||||
<script>
|
<script>
|
||||||
*!*
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
alert( 'Документ и все ресурсы загружены' );
|
alert('Page loaded');
|
||||||
|
|
||||||
|
// image is loaded at this time
|
||||||
|
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
|
||||||
};
|
};
|
||||||
*/!*
|
|
||||||
</script>
|
</script>
|
||||||
<iframe src="https://example.com/" style="height:60px"></iframe>
|
|
||||||
<img src="https://js.cx/clipart/yozhik.jpg?speed=1">
|
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
|
||||||
```
|
```
|
||||||
|
|
||||||
## window.onunload
|
## window.onunload
|
||||||
|
|
||||||
Когда человек уходит со страницы или закрывает окно, на `window` срабатывает событие `unload`. В нём можно сделать что-то, не требующее ожидания, например, закрыть вспомогательные popup-окна, но отменить сам переход нельзя.
|
When a visitor leaves the page, the `unload` event triggers on `window`. We can do something there that doesn't involve a delay, like closing related popup windows. But we can't cancel the transition to another page.
|
||||||
|
|
||||||
Это позволяет другое событие -- `onbeforeunload`, которое поэтому используется гораздо чаще.
|
For that we should use another event -- `onbeforeunload`.
|
||||||
|
|
||||||
## window.onbeforeunload [#window.onbeforeunload]
|
## window.onbeforeunload [#window.onbeforeunload]
|
||||||
|
|
||||||
Если посетитель инициировал переход на другую страницу или нажал "закрыть окно", то обработчик `onbeforeunload` может приостановить процесс и спросить подтверждение.
|
If a visitor initiated leaving the page or tries to close the window, the `beforeunload` handler can ask for additional confirmation.
|
||||||
|
|
||||||
Для этого ему нужно вернуть строку, которую браузеры покажут посетителю, спрашивая -- нужно ли переходить.
|
It needs to return the string with the question. The browser will show it.
|
||||||
|
|
||||||
Например:
|
For instance:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
window.onbeforeunload = function() {
|
window.onbeforeunload = function() {
|
||||||
return "Данные не сохранены. Точно перейти?";
|
return "There are unsaved changes. Leave now?";
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
```warn header="Firefox игнорирует текст, он показывает своё сообщение"
|
|
||||||
Firefox игнорирует текст, а всегда показывает своё сообщение. Это сделано в целях большей безопасности посетителя, чтобы его нельзя было ввести в заблуждение сообщением.
|
|
||||||
```
|
|
||||||
|
|
||||||
```online
|
```online
|
||||||
Кликните на кнопку в `IFRAME'е` ниже, чтобы поставить обработчик, а затем по ссылке, чтобы увидеть его в действии:
|
Click on the button in `<iframe>` below to set the handler, and then click the link to see it in action:
|
||||||
|
|
||||||
[iframe src="window-onbeforeunload" border="1" height="80" link]
|
[iframe src="window-onbeforeunload" border="1" height="80" link edit]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Эмуляция DOMContentLoaded для IE8-
|
```warn header="Some browsers ignore the text and show their own message instead"
|
||||||
|
Some browsers like Chrome and Firefox ignore the string and shows its own message instead. That's for sheer safety, to protect the user from potentially misleading and hackish messages.
|
||||||
Прежде чем что-то эмулировать, заметим, что альтернативой событию `onDOMContentLoaded` является вызов функции `init` из скрипта в самом конце `BODY`, когда основная часть DOM уже готова:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<body>
|
|
||||||
...
|
|
||||||
<script>
|
|
||||||
init();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Причина, по которой обычно предпочитают именно событие -- одна: удобство. Вешается обработчик и не надо ничего писать в конец `BODY`.
|
## Summary
|
||||||
|
|
||||||
### Мини-скрипт documentReady
|
Page lifecycle events:
|
||||||
Если вы всё же хотите использовать `onDOMContentLoaded` кросс-браузерно, то нужно либо подключить какой-нибудь фреймворк -- почти все предоставляют такой функционал, либо использовать функцию из мини-библиотеки [jquery.documentReady.js](https://github.com/Couto/jquery.parts/blob/master/jquery.documentReady.js).
|
|
||||||
|
|
||||||
Несмотря на то, что в названии содержится слово "jquery", эта библиотечка не требует [jQuery](http://jquery.com). Наоборот, она представляет собой единственную функцию с названием `$`, вызов которой `$(callback)` добавляет обработчик `callback` на `DOMContentLoaded` (можно вызывать много раз), либо, если документ уже загружен -- выполняет его тут же.
|
- `DOMContentLoaded` event triggers on `document` when DOM is ready. We can apply Javascript to elements at this stage.
|
||||||
|
- All scripts are executed except those that are external with `async` or `defer`
|
||||||
|
- Images and other resources may still continue loading.
|
||||||
|
|
||||||
Пример использования:
|
- `load` event on `window` triggers when the page and all resources are loaded. We rarely use it, because there's usually no need to wait for so long.
|
||||||
|
- `beforeload` event on `window` triggers when the user wants to leave the page. If it returns a string, the browser shows a question whether the user really wants to leave or not.
|
||||||
```html run
|
- `unload` event on `window` triggers when the user is finally leaving, in the handler we can only do simple things that do not involve delays or asking a user. Because of that limitation, it's rarely used.
|
||||||
<script src="https://js.cx/script/jquery.documentReady.js"></script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
*!*
|
|
||||||
$(function() {
|
|
||||||
alert( "DOMContentLoaded" );
|
|
||||||
});
|
|
||||||
*/!*
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<img src="https://js.cx/clipart/yozhik.jpg?speed=1">
|
|
||||||
<div>Текст страницы</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Здесь `alert` сработает до загрузки картинки, но после создания DOM, в частности, после появления текста. И так будет для всех браузеров, включая даже очень старые IE.
|
|
||||||
|
|
||||||
````smart header="Как именно эмулируется `DOMContentLoaded`?"
|
|
||||||
Технически, эмуляция `DOMContentLoaded` для старых IE осуществляется очень забавно.
|
|
||||||
|
|
||||||
Основной приём -- это попытка прокрутить документ вызовом:
|
|
||||||
|
|
||||||
```js
|
|
||||||
document.documentElement.doScroll("left");
|
|
||||||
```
|
|
||||||
|
|
||||||
Метод `doScroll` работает только в IE и "методом тыка" было обнаружено, что он бросает исключение, если DOM не полностью создан.
|
|
||||||
|
|
||||||
Поэтому библиотека пытается вызвать прокрутку, если не получается -- через `setTimeout(.., 1)` пытается прокрутить его ещё раз, и так до тех пор, пока действие не перестанет вызывать ошибку. На этом этапе документ считается загрузившимся.
|
|
||||||
|
|
||||||
Внутри фреймов и в очень старых браузерах такой подход может ошибаться, поэтому дополнительно ставится резервный обработчик на `onload`, чтобы уж точно сработал.
|
|
||||||
````
|
|
||||||
|
|
||||||
## Итого
|
|
||||||
|
|
||||||
- Самое востребованное событие из описанных -- без сомнения, `DOMContentLoaded`. Многие страницы сделаны так, что инициализуют интерфейсы именно по этому событию.
|
|
||||||
|
|
||||||
Это удобно, ведь можно в `<head>` написать скрипт, который будет запущен в момент, когда все DOM-элементы доступны.
|
|
||||||
|
|
||||||
С другой стороны, следует иметь в виду, что событие `DOMContentLoaded` будет ждать не только, собственно, HTML-страницу, но и внешние скрипты, подключенные тегом `<script>` без атрибутов `defer/async`, а также стили перед такими скриптами.
|
|
||||||
|
|
||||||
Событие `DOMContentLoaded` не поддерживается в IE8-, но почти все фреймворки умеют его эмулировать. Если нужна отдельная функция только для кросс-браузерного аналога `DOMContentLoaded` -- можно использовать [jquery.documentReady.js](https://github.com/Couto/jquery.parts/blob/master/jquery.documentReady.js).
|
|
||||||
- Событие `window.onload` используют редко, поскольку обычно нет нужды ждать подгрузки *всех* ресурсов. Если же нужен конкретный ресурс (картинка или ифрейм), то можно поставить событие `onload` непосредственно на нём, мы посмотрим, как это сделать, далее.
|
|
||||||
- Событие `window.onunload` почти не используется, как правило, оно бесполезно -- мало что можно сделать, зная, что окно браузера прямо сейчас закроется.
|
|
||||||
- Гораздо чаще применяется `window.onbeforeunload` -- это де-факто стандарт для того, чтобы проверить, сохранил ли посетитель данные, действительно ли он хочет покинуть страницу. В системах редактирования документов оно используется повсеместно.
|
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<script>
|
<script>
|
||||||
function setHandler() {
|
function setHandler() {
|
||||||
window.onbeforeunload = function() {
|
window.onbeforeunload = function() {
|
||||||
return "Данные не сохранены. Точно перейти?";
|
return "There are unsaved changes. Leave now?";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button onclick="setHandler()">Поставить window.onbeforeunload</button>
|
<button onclick="setHandler()">Set window.onbeforeunload</button>
|
||||||
|
|
||||||
<a href="http://example.com">Уйти на EXAMPLE.COM</a>
|
<a href="http://example.com">Leave for EXAMPLE.COM</a>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -1,4 +1,4 @@
|
||||||
# Загрузка скриптов, картинок, фреймов: onload и onerror
|
# Resource loading: onload and onerror
|
||||||
|
|
||||||
Браузер позволяет отслеживать загрузку внешних ресурсов -- скриптов, ифреймов, картинок и других.
|
Браузер позволяет отслеживать загрузку внешних ресурсов -- скриптов, ифреймов, картинок и других.
|
||||||
|
|
||||||
|
@ -225,4 +225,3 @@ script.onreadystatechange = function() {
|
||||||
|
|
||||||
Отловить загрузку `<iframe>`
|
Отловить загрузку `<iframe>`
|
||||||
: Поддерживается только обработчик `onload`. Он сработает, когда `IFRAME` загрузится, со всеми подресурсами, а также в случае ошибки.
|
: Поддерживается только обработчик `onload`. Он сработает, когда `IFRAME` загрузится, со всеми подресурсами, а также в случае ошибки.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Keyboard: keydown and keyup
|
# Keyboard: keydown and keyup
|
||||||
|
|
||||||
Let's study keyboard events now.
|
Let's study keyboard events.
|
||||||
|
|
||||||
Before we start, please note that on modern devices there are other ways to "input something" then just a keyboard. For instance, people use speech recognition (tap microphone, say something, see it entered) or copy/paste with a mouse.
|
Before we start, please note that on modern devices there are other ways to "input something" then just a keyboard. For instance, people use speech recognition (tap microphone, say something, see it entered) or copy/paste with a mouse.
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ As you read on, if you want to try things out -- return to the stand and press k
|
||||||
|
|
||||||
## Keydown and keyup
|
## Keydown and keyup
|
||||||
|
|
||||||
The `keydown` events happens when a key is pressed, and then `keyup` -- when it's released.
|
The `keydown` events happens when a key is pressed down, and then `keyup` -- when it's released.
|
||||||
|
|
||||||
### event.code and event.key
|
### event.code and event.key
|
||||||
|
|
||||||
|
@ -49,7 +49,8 @@ The `event.key` is exactly the character, and it will be different. But `event.c
|
||||||
Key codes like `"KeyZ"` in the example above are described in the [UI Events code specification](https://www.w3.org/TR/uievents-code/).
|
Key codes like `"KeyZ"` in the example above are described in the [UI Events code specification](https://www.w3.org/TR/uievents-code/).
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
- Letter keys have codes `"Key<letter>"`
|
- Letter keys have codes `"Key<letter>"`: `"KeyA"`, `"KeyB"` etc.
|
||||||
|
- Digit keys have codes: `"Digit<number>"`: `"Digit0"`, `"Digit1"` etc.
|
||||||
- Special keys are coded by their names: `"Enter"`, `"Backspace"`, `"Tab"` etc.
|
- Special keys are coded by their names: `"Enter"`, `"Backspace"`, `"Tab"` etc.
|
||||||
|
|
||||||
See [alphanumeric section](https://www.w3.org/TR/uievents-code/#key-alphanumeric-section) for more examples, or just try the [teststand](#keyboard-test-stand) above.
|
See [alphanumeric section](https://www.w3.org/TR/uievents-code/#key-alphanumeric-section) for more examples, or just try the [teststand](#keyboard-test-stand) above.
|
||||||
|
@ -102,42 +103,60 @@ For all repeating keys the event object has `event.return=true`.
|
||||||
|
|
||||||
## Default actions
|
## Default actions
|
||||||
|
|
||||||
Default actions vary, as there are many possible things that may be initiated by keyboard:
|
Default actions vary, as there are many possible things that may be initiated by keyboard.
|
||||||
|
|
||||||
For instance:
|
For instance:
|
||||||
|
|
||||||
- A character appears on the screen (the most obvious one).
|
- A character appears on the screen (the most obvious outcome).
|
||||||
- A character is deleted (`key:Delete` key).
|
- A character is deleted (`key:Delete` key).
|
||||||
- The page is scrolled (`key:PageDown` key).
|
- The page is scrolled (`key:PageDown` key).
|
||||||
- The browser opens the "Save Page" dialog (`key:Ctrl+S`)
|
- The browser opens the "Save Page" dialog (`key:Ctrl+S`)
|
||||||
- ...and so on.
|
- ...and so on.
|
||||||
|
|
||||||
Preventing the default actions cancels most of them, with the sole exception of OS-based special keys.
|
Preventing the default action on `keydown` can cancel most of them, with the exception of OS-based special keys. For instance, on Windows `key:Alt+F4` closes the current browser window. And there's no way to stop it by preventing the default action in JavaScript.
|
||||||
|
|
||||||
For instance, on Windows `key:Alt+F4` closes the current browser window. And there's no way to stop it by preventing the default action in JavaScript.
|
For instance, the `<input>` below expects a phone number, so it does not accept keys except digits, `+`, `()` or `-`:
|
||||||
|
|
||||||
For instance, we can't use keyboard to enter something into the `<input>` below:
|
|
||||||
|
|
||||||
```html run
|
```html run
|
||||||
<input *!*onkeydown="return false"*/!* placeholder="No keyboard input" type="text">
|
<script>
|
||||||
|
function checkPhoneKey(key) {
|
||||||
|
return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<input *!*onkeydown="return checkPhoneKey(event.key)"*/!* placeholder="Phone, please" type="tel">
|
||||||
```
|
```
|
||||||
|
|
||||||
If we type-in something, it's just ignored. Please note that special combinations like `Ctrl+V` (paste) also don't work. But using a mouse and right-click + Paste still can add the text. So checking in `keydown` doesn't work as a 100% reliable filter.
|
Please note that special keys like `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V` do not work. That's a side-effect effect of the strict filter `checkPhoneKey`.
|
||||||
|
|
||||||
|
Let's relax it a little bit:
|
||||||
|
|
||||||
|
|
||||||
|
```html run
|
||||||
|
<script>
|
||||||
|
function checkPhoneKey(key) {
|
||||||
|
return (key >= '0' && key <= '9') || key == '+' || key == '(' || key == ')' || key == '-' ||
|
||||||
|
key == 'ArrowLeft' || key == 'ArrowRight' || key == 'Delete' || key == 'Backspace';
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<input *!*onkeydown="return checkPhoneKey(event.key)"*/!* placeholder="Phone, please" type="tel">
|
||||||
|
```
|
||||||
|
|
||||||
|
Now arrows and deletion works well.
|
||||||
|
|
||||||
|
But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because that usually works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid.
|
||||||
|
|
||||||
## Legacy
|
## Legacy
|
||||||
|
|
||||||
In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `which` properties of the event object.
|
In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `which` properties of the event object.
|
||||||
|
|
||||||
But there were so many incompatibilities between browsers that all of them are deprecated and removed from the standard. So the old code still works (and sometimes works around a bunch of quirks), but there's totally no need to use those any more.
|
There were so many browser incompatibilities that standard developers decided to deprecate all of them and remove from the specification. The old code still works, as the browser keep supporting them, but there's totally no need to use those any more.
|
||||||
|
|
||||||
There was time when this chapter included their detailed description. But as of now we can forget about those.
|
There was time when this chapter included their detailed description. But as of now we can forget about those.
|
||||||
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
All keys yield keyboard events -- be it symbol keys or special keys like `key:Shift` or `key:Ctrl`.
|
Pressing a key always generates a keyboard event, be it symbol keys or special keys like `key:Shift` or `key:Ctrl` and so on. The only exception is `key:Fn` key that is sometimes present on laptop keyboards. There's no keyboard event for it, because it's often implemented on lower level than OS.
|
||||||
|
|
||||||
The only exception is `key:Fn` key that is sometimes present on laptop keyboards. There's no keyboard event for it, because it's often implemented on lower-level than OS.
|
|
||||||
|
|
||||||
Keyboard events:
|
Keyboard events:
|
||||||
|
|
||||||
|
@ -146,9 +165,9 @@ Keyboard events:
|
||||||
|
|
||||||
Main keyboard event properties:
|
Main keyboard event properties:
|
||||||
|
|
||||||
- `code` -- the "key code", specific to the physical location of the key on keyboard.
|
- `code` -- the "key code" (`"KeyA"`, `"ArrowLeft"` and so on), specific to the physical location of the key on keyboard.
|
||||||
- `key` -- the character, for non-character keys a "code value", usually same as `code`.
|
- `key` -- the character (`"A"`, `"a"` and so on), for non-character keys usually has the same value as `code`.
|
||||||
|
|
||||||
In the past, keyboard events were sometimes used to track user input in form fields. That's not the case any more, because we have `input` and `change` events for that (to be covered later). They are better, because they work for all ways of input, including mouse or speech recognition.
|
In the past, keyboard events were sometimes used to track user input in form fields. That's not the case any more, because we have `input` and `change` events for that (to be covered later). They are better, because they work for all ways of input, including mouse or speech recognition.
|
||||||
|
|
||||||
We still should use them when we really want keyboard. For example, to react on hotkeys or special keys.
|
We should use keyboard events when we really want keyboard. For example, to react on hotkeys or special keys.
|
||||||
|
|
|
@ -13,7 +13,7 @@ Here's a small function to show the current scroll:
|
||||||
```js autorun
|
```js autorun
|
||||||
window.addEventListener('scroll', function() {
|
window.addEventListener('scroll', function() {
|
||||||
document.getElementById('showScroll').innerHTML = pageYOffset + 'px';
|
document.getElementById('showScroll').innerHTML = pageYOffset + 'px';
|
||||||
}
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```online
|
```online
|
||||||
|
|