up
This commit is contained in:
parent
706b1f26b2
commit
0873d43d72
62 changed files with 1695 additions and 142 deletions
|
@ -1,267 +0,0 @@
|
|||
# Page lifecycle: DOMContentLoaded, load, beforeunload, unload
|
||||
|
||||
The lifecycle of an HTML page has three important events:
|
||||
|
||||
- `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).
|
||||
- `beforeunload/unload` -- when the user is leaving the page.
|
||||
|
||||
Each event may be useful:
|
||||
|
||||
- `DOMContentLoaded` event -- DOM is ready, so the handler can lookup DOM nodes, initialize the interface.
|
||||
- `load` event -- additional resources are loaded, we can get image sizes (if not specified in HTML/CSS) etc.
|
||||
- `beforeunload/unload` event -- the user is leaving: we can check if the user saved the changes and ask them whether they really want to leave.
|
||||
|
||||
Let's explore the details of these events.
|
||||
|
||||
## DOMContentLoaded
|
||||
|
||||
The `DOMContentLoaded` event happens on the `document` object.
|
||||
|
||||
We must use `addEventListener` to catch it:
|
||||
|
||||
```js
|
||||
document.addEventListener("DOMContentLoaded", ready);
|
||||
```
|
||||
|
||||
For instance:
|
||||
|
||||
```html run height=200 refresh
|
||||
<script>
|
||||
function ready() {
|
||||
alert('DOM is ready');
|
||||
|
||||
// image is not yet loaded (unless was cached), so the size is 0x0
|
||||
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
|
||||
}
|
||||
|
||||
*!*
|
||||
document.addEventListener("DOMContentLoaded", ready);
|
||||
*/!*
|
||||
</script>
|
||||
|
||||
<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 and does not wait for the image to load. So `alert` shows zero sizes.
|
||||
|
||||
At the first sight `DOMContentLoaded` event is very simple. The DOM tree is ready -- here's the event. But there are few peculiarities.
|
||||
|
||||
### DOMContentLoaded and scripts
|
||||
|
||||
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 (with `src`) also put DOM building to pause while the script is loading and executing. So `DOMContentLoaded` waits for external scripts as well.
|
||||
|
||||
The only exception are external scripts with `async` and `defer` attributes. They tell the browser to continue processing without waiting for the scripts. This lets the user see the page before scripts finish loading, which is good for performance.
|
||||
|
||||
### Scripts with `async` and `defer`
|
||||
|
||||
Attributes `async` and `defer` work only for external scripts. They are ignored if there's no `src`.
|
||||
|
||||
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 while the document has not yet been fully downloaded. That happens if scripts are small or cached, and the document is long enough. | Scripts with `defer` execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. |
|
||||
|
||||
So `async` is used for independent scripts, like counters or ads, that don't need to access page content. And their relative execution order does not matter.
|
||||
|
||||
While `defer` is used for scripts that need DOM and/or their relative execution order is important.
|
||||
|
||||
### DOMContentLoaded and styles
|
||||
|
||||
External style sheets don't affect DOM, and so `DOMContentLoaded` does not wait for them.
|
||||
|
||||
But there's a pitfall: if we have a script after the style, then that script must wait for the stylesheet to execute:
|
||||
|
||||
```html
|
||||
<link type="text/css" rel="stylesheet" href="style.css">
|
||||
<script>
|
||||
// the script doesn't not execute until the stylesheet is loaded
|
||||
alert(getComputedStyle(document.body).marginTop);
|
||||
</script>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
As `DOMContentLoaded` waits for scripts, it now waits for styles before them as well.
|
||||
|
||||
### Built-in browser autofill
|
||||
|
||||
Firefox, Chrome and Opera autofill forms on `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).
|
||||
|
||||
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.
|
||||
|
||||
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]
|
||||
|
||||
The `load` event on the `window` object triggers when the whole page is loaded including styles, images and other resources.
|
||||
|
||||
The example below correctly shows image sizes, because `window.onload` waits for all images:
|
||||
|
||||
```html run height=200 refresh
|
||||
<script>
|
||||
window.onload = function() {
|
||||
alert('Page loaded');
|
||||
|
||||
// image is loaded at this time
|
||||
alert(`Image size: ${img.offsetWidth}x${img.offsetHeight}`);
|
||||
};
|
||||
</script>
|
||||
|
||||
<img id="img" src="https://en.js.cx/clipart/train.gif?speed=1&cache=0">
|
||||
```
|
||||
|
||||
## window.onunload
|
||||
|
||||
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.
|
||||
|
||||
This event is a good place to send out analytics.
|
||||
|
||||
E.g. we have a script that gathers some data about mouse clicks, scrolls, viewed page areas -- the statistics that can help us to see what users want.
|
||||
|
||||
Then `onunload` is the best time to send it out. Regular networking methods such as [fetch](info:fetch-basics) or [XMLHttpRequest](info:xmlhttprequest) don't work well, because we're in the process of leaving the page.
|
||||
|
||||
So, there exist `navigator.sendBeacon(url, data)` method for such needs, described in the specification <https://w3c.github.io/beacon/>.
|
||||
|
||||
It sends the data in background. The transition to another page is not delayed: the browser leaves the page and performs `sendBeacon` in background.
|
||||
|
||||
Here's how to use it:
|
||||
```js
|
||||
let analyticsData = { /* object with gathered data */ };
|
||||
|
||||
window.addEventListener("unload", function() {
|
||||
navigator.sendBeacon("/analytics", JSON.stringify(analyticsData));
|
||||
};
|
||||
```
|
||||
|
||||
- The request is sent as POST.
|
||||
- We can send not only a string, but also forms and other formats, as described in the chapter <info:fetch-basics>, but usually it's a stringified object.
|
||||
- The data is limited by 64kb.
|
||||
|
||||
When the `sendBeacon` request is finished, the browser probably has already left the document, so there's no way to get server response (which is usually empty for analytics).
|
||||
|
||||
If we want to cancel the transition to another page, we can't do it here. But we can use another event -- `onbeforeunload`.
|
||||
|
||||
## window.onbeforeunload [#window.onbeforeunload]
|
||||
|
||||
If a visitor initiated navigation away from the page or tries to close the window, the `beforeunload` handler asks for additional confirmation.
|
||||
|
||||
It may return a string with the question. Historically browsers used to show it, but as of now only some of them do. That's because certain webmasters abused this event handler by showing misleading and hackish messages.
|
||||
|
||||
You can try it by running this code and then reloading the page.
|
||||
|
||||
```js run
|
||||
window.onbeforeunload = function() {
|
||||
return "There are unsaved changes. Leave now?";
|
||||
};
|
||||
```
|
||||
|
||||
```online
|
||||
Or you can click on the button in `<iframe>` below to set the handler, and then click the link:
|
||||
|
||||
[iframe src="window-onbeforeunload" border="1" height="80" link edit]
|
||||
```
|
||||
|
||||
## readyState
|
||||
|
||||
What happens if we set the `DOMContentLoaded` handler after the document is loaded?
|
||||
|
||||
Naturally, it never runs.
|
||||
|
||||
There are cases when we are not sure whether the document is ready or not, for instance an external script with `async` attribute loads and runs asynchronously. Depending on the network, it may load and execute before the document is complete or after that, we can't be sure. So we should be able to know the current state of the document.
|
||||
|
||||
The `document.readyState` property gives us information about it. There are 3 possible values:
|
||||
|
||||
- `"loading"` -- the document is loading.
|
||||
- `"interactive"` -- the document was fully read.
|
||||
- `"complete"` -- the document was fully read and all resources (like images) are loaded too.
|
||||
|
||||
So we can check `document.readyState` and setup a handler or execute the code immediately if it's ready.
|
||||
|
||||
Like this:
|
||||
|
||||
```js
|
||||
function work() { /*...*/ }
|
||||
|
||||
if (document.readyState == 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', work);
|
||||
} else {
|
||||
work();
|
||||
}
|
||||
```
|
||||
|
||||
There's a `readystatechange` event that triggers when the state changes, so we can print all these states like this:
|
||||
|
||||
```js run
|
||||
// current state
|
||||
console.log(document.readyState);
|
||||
|
||||
// print state changes
|
||||
document.addEventListener('readystatechange', () => console.log(document.readyState));
|
||||
```
|
||||
|
||||
The `readystatechange` event is an alternative mechanics of tracking the document loading state, it appeared long ago. Nowadays, it is rarely used, but let's cover it for completeness.
|
||||
|
||||
What is the place of `readystatechange` among other events?
|
||||
|
||||
To see the timing, here's a document with `<iframe>`, `<img>` and handlers that log events:
|
||||
|
||||
```html
|
||||
<script>
|
||||
function log(text) { /* output the time and message */ }
|
||||
log('initial readyState:' + document.readyState);
|
||||
|
||||
document.addEventListener('readystatechange', () => log('readyState:' + document.readyState));
|
||||
document.addEventListener('DOMContentLoaded', () => log('DOMContentLoaded'));
|
||||
|
||||
window.onload = () => log('window onload');
|
||||
</script>
|
||||
|
||||
<iframe src="iframe.html" onload="log('iframe onload')"></iframe>
|
||||
|
||||
<img src="http://en.js.cx/clipart/train.gif" id="img">
|
||||
<script>
|
||||
img.onload = () => log('img onload');
|
||||
</script>
|
||||
```
|
||||
|
||||
The working example is [in the sandbox](sandbox:readystate).
|
||||
|
||||
The typical output:
|
||||
1. [1] initial readyState:loading
|
||||
2. [2] readyState:interactive
|
||||
3. [2] DOMContentLoaded
|
||||
4. [3] iframe onload
|
||||
5. [4] img onload
|
||||
6. [4] readyState:complete
|
||||
7. [4] window onload
|
||||
|
||||
The numbers in square brackets denote the approximate time of when it happens. The real time is a bit greater, but events labeled with the same digit happen approximately at the same time (+- a few ms).
|
||||
|
||||
- `document.readyState` becomes `interactive` right before `DOMContentLoaded`. These two events actually mean the same.
|
||||
- `document.readyState` becomes `complete` when all resources (`iframe` and `img`) are loaded. Here we can see that it happens in about the same time as `img.onload` (`img` is the last resource) and `window.onload`. Switching to `complete` state means the same as `window.onload`. The difference is that `window.onload` always works after all other `load` handlers.
|
||||
|
||||
|
||||
## Lifecycle events summary
|
||||
|
||||
Page lifecycle events:
|
||||
|
||||
- `DOMContentLoaded` event triggers on `document` when DOM is ready. We can apply JavaScript to elements at this stage.
|
||||
- All inline scripts and scripts with `defer` are already executed.
|
||||
- Async scripts may execute both before and after the event, depends on when they load.
|
||||
- Images and other resources may also 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.
|
||||
- `beforeunload` 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.
|
||||
- `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.
|
||||
- `document.readyState` is the current state of the document, changes can be tracked in the `readystatechange` event:
|
||||
- `loading` -- the document is loading.
|
||||
- `interactive` -- the document is parsed, happens at about the same time as `DOMContentLoaded`, but before it.
|
||||
- `complete` -- the document and resources are loaded, happens at about the same time as `window.onload`, but before it.
|
|
@ -1,9 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
test iframe
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load diff
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
function setHandler() {
|
||||
window.onbeforeunload = function() {
|
||||
return "There are unsaved changes. Leave now?";
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onclick="setHandler()">Set window.onbeforeunload</button>
|
||||
|
||||
<a href="http://example.com">Leave for EXAMPLE.COM</a>
|
||||
</body>
|
||||
</html>
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
The algorithm:
|
||||
1. Make `img` for every source.
|
||||
2. Add `onload/onerror` for every image.
|
||||
3. Increase the counter when either `onload` or `onerror` triggers.
|
||||
4. When the counter value equals to the sources count -- we're done: `callback()`.
|
|
@ -1,54 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function preloadImages(sources, callback) {
|
||||
let counter = 0;
|
||||
|
||||
function onLoad() {
|
||||
counter++;
|
||||
if (counter == sources.length) callback();
|
||||
}
|
||||
|
||||
for(let source of sources) {
|
||||
let img = document.createElement('img');
|
||||
img.onload = img.onerror = onLoad;
|
||||
img.src = source;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- The test ----------
|
||||
|
||||
let sources = [
|
||||
"https://en.js.cx/images-load/1.jpg",
|
||||
"https://en.js.cx/images-load/2.jpg",
|
||||
"https://en.js.cx/images-load/3.jpg"
|
||||
];
|
||||
|
||||
// add random characters to prevent browser caching
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
sources[i] += '?' + Math.random();
|
||||
}
|
||||
|
||||
// for each image,
|
||||
// let's create another img with the same src and check that we have its width immediately
|
||||
function testLoaded() {
|
||||
let widthSum = 0;
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
let img = document.createElement('img');
|
||||
img.src = sources[i];
|
||||
widthSum += img.width;
|
||||
}
|
||||
alert(widthSum);
|
||||
}
|
||||
|
||||
// should output 300
|
||||
preloadImages(sources, testLoaded);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,43 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
function preloadImages(sources, callback) {
|
||||
/* your code */
|
||||
}
|
||||
|
||||
// ---------- The test ----------
|
||||
|
||||
let sources = [
|
||||
"https://en.js.cx/images-load/1.jpg",
|
||||
"https://en.js.cx/images-load/2.jpg",
|
||||
"https://en.js.cx/images-load/3.jpg"
|
||||
];
|
||||
|
||||
// add random characters to prevent browser caching
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
sources[i] += '?' + Math.random();
|
||||
}
|
||||
|
||||
// for each image,
|
||||
// let's create another img with the same src and check that we have its width immediately
|
||||
function testLoaded() {
|
||||
let widthSum = 0;
|
||||
for (let i = 0; i < sources.length; i++) {
|
||||
let img = document.createElement('img');
|
||||
img.src = sources[i];
|
||||
widthSum += img.width;
|
||||
}
|
||||
alert(widthSum);
|
||||
}
|
||||
|
||||
// every image is 100x100, the total width should be 300
|
||||
preloadImages(sources, testLoaded);
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,36 +0,0 @@
|
|||
importance: 4
|
||||
|
||||
---
|
||||
|
||||
# Load images with a callback
|
||||
|
||||
Normally, images are loaded when they are created. So when we add `<img>` to the page, the user does not see the picture immediately. The browser needs to load it first.
|
||||
|
||||
To show an image immediately, we can create it "in advance", like this:
|
||||
|
||||
```js
|
||||
let img = document.createElement('img');
|
||||
img.src = 'my.jpg';
|
||||
```
|
||||
|
||||
The browser starts loading the image and remembers it in the cache. Later, when the same image appears in the document (no matter how), it shows up immediately.
|
||||
|
||||
**Create a function `preloadImages(sources, callback)` that loads all images from the array `sources` and, when ready, runs `callback`.**
|
||||
|
||||
For instance, this will show an `alert` after the images are loaded:
|
||||
|
||||
```js
|
||||
function loaded() {
|
||||
alert("Images loaded")
|
||||
}
|
||||
|
||||
preloadImages(["1.jpg", "2.jpg", "3.jpg"], loaded);
|
||||
```
|
||||
|
||||
In case of an error, the function should still assume the picture "loaded".
|
||||
|
||||
In other words, the `callback` is executed when all images are either loaded or errored out.
|
||||
|
||||
The function is useful, for instance, when we plan to show a gallery with many scrollable images, and want to be sure that all images are loaded.
|
||||
|
||||
In the source document you can find links to test images, and also the code to check whether they are loaded or not. It should output `300`.
|
|
@ -1,91 +0,0 @@
|
|||
# Resource loading: onload and onerror
|
||||
|
||||
The browser allows to track the loading of external resources -- scripts, iframes, pictures and so on.
|
||||
|
||||
There are two events for it:
|
||||
|
||||
- `onload` -- successful load,
|
||||
- `onerror` -- an error occurred.
|
||||
|
||||
## Loading a script
|
||||
|
||||
Let's say we need to call a function that resides in an external script.
|
||||
|
||||
We can load it dynamically, like this:
|
||||
|
||||
```js
|
||||
let script = document.createElement('script');
|
||||
script.src = "my.js";
|
||||
|
||||
document.head.append(script);
|
||||
```
|
||||
|
||||
...But how to run the function that is declared inside that script? We need to wait until the script loads, and only then we can call it.
|
||||
|
||||
### script.onload
|
||||
|
||||
The main helper is the `load` event. It triggers after the script was loaded and executed.
|
||||
|
||||
For instance:
|
||||
|
||||
```js run untrusted
|
||||
let script = document.createElement('script');
|
||||
|
||||
// can load any script, from any domain
|
||||
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js"
|
||||
document.head.append(script);
|
||||
|
||||
*!*
|
||||
script.onload = function() {
|
||||
// the script creates a helper function "_"
|
||||
alert(_); // the function is available
|
||||
};
|
||||
*/!*
|
||||
```
|
||||
|
||||
So in `onload` we can use script variables, run functions etc.
|
||||
|
||||
...And what if the loading failed? For instance, there's no such script (error 404) or the server or the server is down (unavailable).
|
||||
|
||||
### script.onerror
|
||||
|
||||
Errors that occur during the loading (but not execution) of the script can be tracked on `error` event.
|
||||
|
||||
For instance, let's request a script that doesn't exist:
|
||||
|
||||
```js run
|
||||
let script = document.createElement('script');
|
||||
script.src = "https://example.com/404.js"; // no such script
|
||||
document.head.append(script);
|
||||
|
||||
*!*
|
||||
script.onerror = function() {
|
||||
alert("Error loading " + this.src); // Error loading https://example.com/404.js
|
||||
};
|
||||
*/!*
|
||||
```
|
||||
|
||||
Please note that we can't get error details here. We don't know was it error 404 or 500 or something else. Just that the loading failed.
|
||||
|
||||
## Other resources
|
||||
|
||||
The `load` and `error` events also work for other resources. There may be minor differences though.
|
||||
|
||||
For instance:
|
||||
|
||||
`<img>`, `<link>` (external stylesheets)
|
||||
: Both `load` and `error` events work as expected.
|
||||
|
||||
`<iframe>`
|
||||
: Only `load` event when the iframe loading finished. It triggers both for successful load and in case of an error. That's for historical reasons.
|
||||
|
||||
## Summary
|
||||
|
||||
Pictures `<img>`, external styles, scripts and other resources provide `load` and `error` events to track their loading:
|
||||
|
||||
- `load` triggers on a successful load,
|
||||
- `error` triggers on a failed load.
|
||||
|
||||
The only exception is `<iframe>`: for historical reasons it always triggers `load`, for any load completion, even if the page is not found.
|
||||
|
||||
The `readystatechange` event also works for resources, but is rarely used, because `load/error` events are simpler.
|
|
@ -1,3 +1,3 @@
|
|||
# Events in details
|
||||
# UI Events
|
||||
|
||||
Here we cover most important events and details of working with them.
|
||||
Here we cover most important user interface events and how to work with them.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue