components
This commit is contained in:
parent
304d578b54
commit
6fb4aabcba
344 changed files with 669 additions and 406 deletions
|
@ -0,0 +1,6 @@
|
|||
|
||||
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()`.
|
|
@ -0,0 +1,54 @@
|
|||
<!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>
|
|
@ -0,0 +1,43 @@
|
|||
<!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>
|
36
2-ui/5-loading/03-onload-onerror/1-load-img-callback/task.md
Normal file
36
2-ui/5-loading/03-onload-onerror/1-load-img-callback/task.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
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`.
|
206
2-ui/5-loading/03-onload-onerror/article.md
Normal file
206
2-ui/5-loading/03-onload-onerror/article.md
Normal file
|
@ -0,0 +1,206 @@
|
|||
# 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 load a third-party script and call a function that resides there.
|
||||
|
||||
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.
|
||||
|
||||
```smart
|
||||
For our own scripts we could use [Javascript modules](info:modules) here, but they are not widely adopted by third-party libraries.
|
||||
```
|
||||
|
||||
### 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 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 HTTP error details here. We don't know was it error 404 or 500 or something else. Just that the loading failed.
|
||||
|
||||
```warn
|
||||
Events `onload`/`onerror` track only the loading itself.
|
||||
|
||||
Errors during script processing and execution are out of the scope of these events. To track script errors, one can use `window.onerror` global handler.
|
||||
```
|
||||
|
||||
## Other resources
|
||||
|
||||
The `load` and `error` events also work for other resources, basically for any resource that has an external `src`.
|
||||
|
||||
For example:
|
||||
|
||||
```js run
|
||||
let img = document.createElement('img');
|
||||
img.src = "https://js.cx/clipart/train.gif"; // (*)
|
||||
|
||||
img.onload = function() {
|
||||
alert(`Image loaded, size ${img.width}x${img.height}`);
|
||||
};
|
||||
|
||||
img.onerror = function() {
|
||||
alert("Error occured while loading image");
|
||||
};
|
||||
```
|
||||
|
||||
There are some notes though:
|
||||
|
||||
- Most resources start loading when they are added to the document. But `<img>` is an exception. It starts loading when it gets an src `(*)`.
|
||||
- For `<iframe>`, the `iframe.onload` event triggers when the iframe loading finished, both for successful load and in case of an error.
|
||||
|
||||
That's for historical reasons.
|
||||
|
||||
## Crossorigin policy
|
||||
|
||||
There's a rule: scripts from one site can't access contents of the other site. So, e.g. a script at `https://facebook.com` can't read the user's mailbox at `https://gmail.com`.
|
||||
|
||||
Or, to be more precise, one origin (domain/port/protocol triplet) can't access the content from another one. So even if we have a subdomain, or just another port, these are different origins, no access to each other.
|
||||
|
||||
This rule also affects resources from other domains.
|
||||
|
||||
If we're using a script from another domain, and there's an error in it, we can't get error details.
|
||||
|
||||
For example, let's take a script with a single (bad) function call:
|
||||
```js
|
||||
// 📁 error.js
|
||||
noSuchFunction();
|
||||
```
|
||||
|
||||
Now load it from our domain:
|
||||
|
||||
```html run height=0
|
||||
<script>
|
||||
window.onerror = function(message, url, line, col, errorObj) {
|
||||
alert(`${message}\n${url}, ${line}:${col}`);
|
||||
};
|
||||
</script>
|
||||
<script src="/article/onload-onerror/crossorigin/error.js"></script>
|
||||
```
|
||||
|
||||
We can see a good error report, like this:
|
||||
|
||||
```
|
||||
Uncaught ReferenceError: noSuchFunction is not defined
|
||||
https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1
|
||||
```
|
||||
|
||||
Now let's load the same script from another domain:
|
||||
|
||||
```html run height=0
|
||||
<script>
|
||||
window.onerror = function(message, url, line, col, errorObj) {
|
||||
alert(`${message}\n${url}, ${line}:${col}`);
|
||||
};
|
||||
</script>
|
||||
<script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
|
||||
```
|
||||
|
||||
The report is different, like this:
|
||||
|
||||
```
|
||||
Script error.
|
||||
, 0:0
|
||||
```
|
||||
|
||||
Details may vary depeding on the browser, but the idea is same: any information about the internals of a script is hidden. Exactly because it's from another domain.
|
||||
|
||||
Why do we need the details?
|
||||
|
||||
There are many services (and we can build our own) that listen to `window.onerror`, save errors at the server and provide an interface to access and analyze them. That's great, as we can see real errors, triggered by our users. But we can't see any error information for scripts from other domains.
|
||||
|
||||
Similar cross-origin policy (CORS) is enforced for other types of resources as well.
|
||||
|
||||
**To allow cross-origin access, we need `crossorigin` attribute, plus the remote server must provide special headers.**
|
||||
|
||||
There are three levels of cross-origin access:
|
||||
|
||||
1. **No `crossorigin` attribute** -- access prohibited.
|
||||
2. **`crossorigin="anonymous"`** -- access allowed if the server responds with the header `Access-Control-Allow-Origin` with `*` or our origin. Browser does not send authorization information and cookies to remote server.
|
||||
3. **`crossorigin="use-credentials"`** -- access allowed if the server sends back the header `Access-Control-Allow-Origin` with our origin and `Access-Control-Allow-Credentials: true`. Browser sends authorization information and cookies to remote server.
|
||||
|
||||
```smart
|
||||
You can read more about cross-origin access in the chapter <info:fetch-crossorigin>. It describes `fetch` method for network requests, but the policy is exactly the same.
|
||||
|
||||
Such thing as "cookies" is out of our current scope, but you can read about them in the chapter <info:cookie>.
|
||||
```
|
||||
|
||||
In our case, we didn't have any crossorigin attribute. So the cross-origin access was prohibited. Let's add it.
|
||||
|
||||
We can choose between `"anonymous"` (no cookies sent, one server-side header needed) and `"use-credentials"` (sends cookies too, two server-side headers needed).
|
||||
|
||||
If we don't care about cookies, then `"anonymous"` is a way to go:
|
||||
|
||||
```html run height=0
|
||||
<script>
|
||||
window.onerror = function(message, url, line, col, errorObj) {
|
||||
alert(`${message}\n${url}, ${line}:${col}`);
|
||||
};
|
||||
</script>
|
||||
<script *!*crossorigin="anonymous"*/!* src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
|
||||
```
|
||||
|
||||
Now, assuming that the server provides `Access-Control-Allow-Origin` header, everything's fine. We have the full error report.
|
||||
|
||||
## Summary
|
||||
|
||||
Images `<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.
|
|
@ -0,0 +1 @@
|
|||
noSuchFunction();
|
Loading…
Add table
Add a link
Reference in a new issue