Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 22 additions & 23 deletions 2-ui/5-loading/01-onload-ondomcontentloaded/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
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.
- `load` -- not only HTML is loaded, but also all the external resources: images, styles etc.
- `beforeunload/unload` -- 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.
- `load` event -- external resources are loaded, so styles are applied, image sizes are known etc.
- `beforeunload` event -- the user is leaving: we can check if the user saved the changes and ask them whether they really want to leave.
- `unload` -- the user almost left, but we still can initiate some operations, such as sending out statistics.

Expand Down Expand Up @@ -49,7 +49,7 @@ In the example the `DOMContentLoaded` handler runs when the document is loaded,

But it doesn't 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. There are few peculiarities though.
At first sight, the `DOMContentLoaded` event is very simple. The DOM tree is ready -- here's the event. There are few peculiarities though.

### DOMContentLoaded and scripts

Expand All @@ -60,7 +60,7 @@ So DOMContentLoaded definitely happens after such scripts:
```html run
<script>
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM ready!");
alert("DOM ready!");
});
</script>

Expand All @@ -73,11 +73,10 @@ So DOMContentLoaded definitely happens after such scripts:

In the example above, we first see "Library loaded...", and then "DOM ready!" (all scripts are executed).

```warn header="Scripts with `async`, `defer` or `type=\"module\"` don't block DOMContentLoaded"

Script attributes `async` and `defer`, that we'll cover [a bit later](info:script-async-defer), don't block DOMContentLoaded. [Javascript modules](info:modules) behave like `defer`, they don't block it too.

So here we're talking about "regular" scripts, like `<script>...</script>`, or `<script src="..."></script>`.
```warn header="Scripts that don't block DOMContentLoaded"
There are two exceptions from this rule:
1. Scripts with the `async` attribute, that we'll cover [a bit later](info:script-async-defer), don't block `DOMContentLoaded`.
2. Scripts that are generated dynamically with `document.createElement('script')` and then added to the webpage also don't block this event.
```

### DOMContentLoaded and styles
Expand All @@ -86,15 +85,15 @@ External style sheets don't affect DOM, so `DOMContentLoaded` does not wait for

But there's a pitfall. If we have a script after the style, then that script must wait until the stylesheet loads:

```html
```html run
<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.
The reason for this 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.

Expand All @@ -109,13 +108,13 @@ So if `DOMContentLoaded` is postponed by long-loading scripts, then autofill als

## 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 `load` event on the `window` object triggers when the whole page is loaded including styles, images and other resources. This event is available via the `onload` property.

The example below correctly shows image sizes, because `window.onload` waits for all images:

```html run height=200 refresh
<script>
window.onload = function() {
window.onload = function() { // same as window.addEventListener('load', (event) => {
alert('Page loaded');

// image is loaded at this time
Expand Down Expand Up @@ -150,15 +149,15 @@ window.addEventListener("unload", function() {
```

- 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.
- We can send not only a string, but also forms and other formats, as described in the chapter <info:fetch>, 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).

There's also a `keepalive` flag for doing such "after-page-left" requests in [fetch](info:fetch-basics) method for generic network requests. You can find more information in the chapter <info:fetch-api>.
There's also a `keepalive` flag for doing such "after-page-left" requests in [fetch](info:fetch) method for generic network requests. You can find more information in the chapter <info:fetch-api>.


If we want to cancel the transition to another page, we can't do it here. But we can use another event -- `onbeforeunload`.
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]

Expand All @@ -174,7 +173,7 @@ window.onbeforeunload = function() {
};
```

For historical reasons, returning a non-empty string also counts as canceling the event. Some time ago browsers used show it as a message, but as the [modern specification](https://html.spec.whatwg.org/#unloading-documents) says, they shouldn't.
For historical reasons, returning a non-empty string also counts as canceling the event. Some time ago browsers used to show it as a message, but as the [modern specification](https://html.spec.whatwg.org/#unloading-documents) says, they shouldn't.

Here's an example:

Expand Down Expand Up @@ -218,7 +217,7 @@ if (document.readyState == 'loading') {
}
```

There's also `readystatechange` event that triggers when the state changes, so we can print all these states like this:
There's also the `readystatechange` event that triggers when the state changes, so we can print all these states like this:

```js run
// current state
Expand Down Expand Up @@ -273,12 +272,12 @@ The numbers in square brackets denote the approximate time of when it happens. E

Page load events:

- `DOMContentLoaded` event triggers on `document` when DOM is ready. We can apply JavaScript to elements at this stage.
- The `DOMContentLoaded` event triggers on `document` when the DOM is ready. We can apply JavaScript to elements at this stage.
- Script such as `<script>...</script>` or `<script src="..."></script>` block DOMContentLoaded, the browser waits for them to execute.
- 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 we cancel the event, browser asks whether the user really wants to leave (e.g we have unsaved changes).
- `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. We can send out a network request with `navigator.sendBeacon`.
- The `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.
- The `beforeunload` event on `window` triggers when the user wants to leave the page. If we cancel the event, browser asks whether the user really wants to leave (e.g we have unsaved changes).
- The `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. We can send out a network request with `navigator.sendBeacon`.
- `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.
Expand Down
44 changes: 22 additions & 22 deletions 2-ui/5-loading/02-script-async-defer/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

In modern websites, scripts are often "heavier" than HTML: their download size is larger, and processing time is also longer.

When the browser loads HTML and comes across a `<script>...</script>` tag, it can't continue building DOM. It must execute the script right now. The same happens for external scripts `<script src="..."></script>`: the browser must wait until the script downloads, execute it, and only after process the rest of the page.
When the browser loads HTML and comes across a `<script>...</script>` tag, it can't continue building the DOM. It must execute the script right now. The same happens for external scripts `<script src="..."></script>`: the browser must wait until the script downloads, execute it, and only after process the rest of the page.

That leads to two important issues:

1. Scripts can't see DOM elements below them, so can't add handlers etc.
1. Scripts can't see DOM elements below them, so they can't add handlers etc.
2. If there's a bulky script at the top of the page, it "blocks the page". Users can't see the page content till it downloads and runs:

```html run height=100
Expand All @@ -29,11 +29,11 @@ There are some workarounds to that. For instance, we can put a script at the bot
</body>
```

But this solution is far from perfect. For example, the browser actually notices the script (and can start downloading it) only after it downloaded the full HTML document. For long HTML documents, that may be a noticeable delay.
But this solution is far from perfect. For example, the browser notices the script (and can start downloading it) only after it downloaded the full HTML document. For long HTML documents, that may be a noticeable delay.

Such things are invisible for people using very fast connections, but many people in the world still have slower internet speeds and far-from-perfect mobile connectivity.
Such things are invisible for people using very fast connections, but many people in the world still have slow internet speeds and use a far-from-perfect mobile internet connection.

Luckily, there are two `<script>` attributes that solve the problem for us: `defer` and `async`
Luckily, there are two `<script>` attributes that solve the problem for us: `defer` and `async`.

## defer

Expand Down Expand Up @@ -68,34 +68,34 @@ The following example demonstrates that:
```

1. The page content shows up immediately.
2. `DOMContentLoaded` waits for the deferred script. It only triggers when the script `(2)` is downloaded is executed.
2. `DOMContentLoaded` waits for the deferred script. It only triggers when the script `(2)` is downloaded and executed.

Deferred scripts keep their relative order, just like regular scripts.

So, if we have a long script first, and then a smaller one, then the latter one waits.

```html
<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>
<script defer src="https://javascript.info/article/script-async-defer/long.js"></script>
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>
```

```smart header="The small script downloads first, runs second"
Browsers scan the page for scripts and download them in parallel, to improve performance. So in the example above both scripts download in parallel. The `small.js` probably makes it first.

But the specification requres scripts to execute in the document order, so it waits for `long.js` to execute.
But the specification requires scripts to execute in the document order, so it waits for `long.js` to execute.
```

```smart header="The `defer` attribute is only for external scripts"
The `defer` attribute is ignored if the script has no `src`.
The `defer` attribute is ignored if the `<script>` tag has no `src`.
```


## async

The `async` attribute means that a script is completely independant:
The `async` attribute means that a script is completely independent:

- The page doesn't wait for async scripts, the contents is processed and displayed.
- `DOMContentLoaded` and async scripts don't wait each other:
- The page doesn't wait for async scripts, the contents are processed and displayed.
- `DOMContentLoaded` and async scripts don't wait for each other:
- `DOMContentLoaded` may happen both before an async script (if an async script finishes loading after the page is complete)
- ...or after an async script (if an async script is short or was in HTTP-cache)
- Other scripts don't wait for `async` scripts, and `async` scripts don't wait for them.
Expand All @@ -120,16 +120,17 @@ So, if we have several `async` scripts, they may execute in any order. Whatever
2. `DOMContentLoaded` may happen both before and after `async`, no guarantees here.
3. Async scripts don't wait for each other. A smaller script `small.js` goes second, but probably loads before `long.js`, so runs first. That's called a "load-first" order.

Async scripts are great when we integrate an independant third-party script into the page: counters, ads and so on.
Async scripts are great when we integrate an independent third-party script into the page: counters, ads and so on, as they don't depend on our scripts, and our scripts shouldn't wait for them:

```html
<!-- Google Analytics is usually added like this -->
<script async src="https://google-analytics.com/analytics.js"></script>
```


## Dynamic scripts

We can also create a script dynamically using Javascript:
We can also add a script dynamically using JavaScript:

```js run
let script = document.createElement('script');
Expand All @@ -145,7 +146,6 @@ That is:
- They don't wait for anything, nothing waits for them.
- The script that loads first -- runs first ("load-first" order).

We can change the load-first order into the document order by explicitly setting `async` to `false`:

```js run
let script = document.createElement('script');
Expand Down Expand Up @@ -177,7 +177,7 @@ loadScript("/article/script-async-defer/small.js");

## Summary

Both `async` and `defer` have one common thing: they don't block page rendering. So the user can read page content and get acquanted with the page immediately.
Both `async` and `defer` have one common thing: downloading of such scripts doesn't block page rendering. So the user can read page content and get acquainted with the page immediately.

But there are also essential differences between them:

Expand All @@ -187,11 +187,11 @@ But there are also essential differences between them:
| `defer` | *Document order* (as they go in the document). | Execute after the document is loaded and parsed (they wait if needed), right before `DOMContentLoaded`. |

```warn header="Page without scripts should be usable"
Please note that if you're using `defer`, then the page is visible before the script loads and enables all the graphical components.
Please note that if you're using `defer`, then the page is visible *before* the script loads.

So, buttons should be disabled by CSS or by other means, to let the user

In practice, `defer` is used for scripts that need DOM and/or their relative execution order is important.
So the user may read the page, but some graphical components are probably not ready yet.

There should be "loading" indications in the proper places, and disabled buttons should show as such, so the user can clearly see what's ready and what's not.
```

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.
In practice, `defer` is used for scripts that need the whole DOM and/or their relative execution order is important. And `async` is used for independent scripts, like counters or ads. And their relative execution order does not matter.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
}

// for each image,
// let's create another img with the same src and check that we have its width immediately
// let's create another img with the same src and check that we have its width
function testLoaded() {
let widthSum = 0;
for (let i = 0; i < sources.length; i++) {
Expand Down
Loading