This commit is contained in:
Ilya Kantor 2017-03-15 00:43:43 +03:00
parent aeb74092b6
commit 1f61c2ab1d
100 changed files with 4120 additions and 659 deletions

View file

@ -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()`.

View file

@ -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 (var 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>

View file

@ -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 (var 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>

View file

@ -0,0 +1,36 @@
importance: 4
---
# Load images with a callback
Normally, images are loaded when they are created. So i 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`.

View file

@ -1,8 +0,0 @@
# Подсказка
Текст на странице пусть будет изначально `DIV`, с классом `img-replace` и атрибутом `data-src` для картинки.
Функция `replaceImg()` должна искать такие `DIV` и загружать изображение с указанным `src`. По `onload` осуществляется замена `DIV` на картинку.
# Решение

View file

@ -1,49 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
.img-replace {
float: left;
border: 1px solid black;
}
</style>
</head>
<body>
<button onclick="window.location.reload(true)">Перезагрузить ифрейм</button>
<hr>
<div style="width:114px;height:40px;font-size:32px;letter-spacing:3px" data-src="https://js.cx/search/google.png" class="img-replace">
<span style="color:#1A53F7">G</span><span style="color:#E42131">o</span><span style="color:#FEB819">o</span><span style="color:#164AF2">g</span><span style="color:#00a315">l</span><span style="color:#E42131">e</span>
</div>
<div style="width:101px;height:40px;font-size:32px" data-src="https://js.cx/search/yandex.png" class="img-replace">
<span style="color:#F00">Я</span>ндекс
</div>
<div style="width:100;height:40px;font-size:32px;color:#006dd4;font-weight:bold;letter-spacing:3px;font-family:Arial" data-src="bing.png" class="img-replace">bing</div>
<script>
function replaceImg() {
var divs = document.querySelectorAll('div.img-replace');
for (var i = 0; i < divs.length; i++)(function(i) {
var img = document.createElement('img');
img.src = divs[i].getAttribute('data-src');
img.className = 'img-replace';
img.onload = function() {
divs[i].parentNode.replaceChild(img, divs[i]);
}
}(i))
}
setTimeout(replaceImg, 1000); // задержка на 1 сек для демонстрации
</script>
</body>
</html>

View file

@ -1,40 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<style>
.img-replace {
float: left;
border: 1px solid black;
}
</style>
</head>
<body>
<!-- Google -->
<div style="width:114px;height:40px;font-size:32px;letter-spacing:3px" class="img-replace">
<span style="color:#1A53F7">G</span><span style="color:#E42131">o</span><span style="color:#FEB819">o</span><span style="color:#164AF2">g</span><span style="color:#00a315">l</span><span style="color:#E42131">e</span>
</div>
<!-- Яндекс -->
<div style="width:101px;height:40px;font-size:32px" class="img-replace">
<span style="color:#F00">Я</span>ндекс
</div>
<!-- Bing -->
<div style="width:100;height:40px;font-size:32px;color:#006dd4;font-weight:bold;letter-spacing: 3px; font-family:Arial">bing</div>
<hr>
<!-- картинки (для bing картинки специально нет, чтобы протестировать случай "загрузка не удалась") -->
<img src="https://js.cx/search/yandex.png" width="114" height="40" alt="Яндекс">
<img src="https://js.cx/search/google.png" width="101" height="40" alt="Google">
<img src="https://js.cx/search/bing.png" width="101" height="40" alt="Файла нет (bing)">
</body>
</html>

View file

@ -1,20 +0,0 @@
importance: 5
---
# Красивый "ALT"
Обычно, до того как изображение загрузится (или при отключенных картинках), посетитель видит пустое место с текстом из "ALT". Но этот атрибут не допускает HTML-форматирования.
При мобильном доступе скорость небольшая, и хочется, чтобы посетитель сразу видел красивый текст.
**Реализуйте "красивый" (HTML) аналог `alt` при помощи CSS/JavaScript, который затем будет заменён картинкой сразу же как только она загрузится.** А если загрузка не состоится -- то не заменён.
Демо: (нажмите "перезагрузить", чтобы увидеть процесс загрузки и замены)
[iframe src="solution" height="100"]
Картинки для `bing` специально нет, так что текст остается "как есть".
Исходный документ содержит разметку текста и ссылки на изображения.

View file

@ -1,5 +0,0 @@
Создайте переменную-счетчик для подсчёта количества загруженных картинок, и увеличивайте при каждом `onload/onerror`.
Когда счетчик станет равен количеству картинок -- вызывайте `callback`.

View file

@ -1,57 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function preloadImages(sources, callback) {
var counter = 0;
function onLoad() {
counter++;
if (counter == sources.length) callback();
}
for (var i = 0; i < sources.length; i++) {
var img = document.createElement('img');
// сначала onload/onerror, затем src - важно для IE8-
img.onload = img.onerror = onLoad;
img.src = sources[i];
}
}
// ---------- Проверка ----------
var sources = [
"https://js.cx/images-load/1.jpg",
"https://js.cx/images-load/2.jpg",
"https://js.cx/images-load/3.jpg"
];
for (var i = 0; i < sources.length; i++) {
sources[i] += '?' + Math.random();
}
function testLoaded() {
var widthSum = 0;
for (var i = 0; i < sources.length; i++) {
var img = document.createElement('img');
img.src = sources[i];
widthSum += img.width;
}
alert(widthSum); // 300!
}
// до загрузки - 0
testLoaded();
// после загрузки - 300
preloadImages(sources, testLoaded);
</script>
</body>
</html>

View file

@ -1,50 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function preloadImages(sources, callback) {
/* ваш код */
}
// ---------- Проверка ----------
/* файлы для загрузки */
var sources = [
"https://js.cx/images-load/1.jpg",
"https://js.cx/images-load/2.jpg",
"https://js.cx/images-load/3.jpg"
];
for (var i = 0; i < sources.length; i++) {
sources[i] += '?' + Math.random(); // добавляем параметр, чтобы без кеша (для теста)
}
/** если картинка загружена, то можно будет сразу получить её ширину
* создадим все картинки и проверим, есть ли у них ширина
*/
function testLoaded() {
var widthSum = 0;
for (var i = 0; i < sources.length; i++) {
var img = document.createElement('img');
img.src = sources[i];
widthSum += img.width;
}
// каждое изображение 100x100, общая ширина должна быть 300px
alert(widthSum);
}
// до загрузки - выведет 0
testLoaded();
// после загрузки - выведет 300
preloadImages(sources, testLoaded);
</script>
</body>
</html>

View file

@ -1,20 +0,0 @@
importance: 4
---
# Загрузить изображения с коллбэком
Создайте функцию `preloadImages(sources, callback)`, которая предзагружает изображения из массива `sources`, и после загрузки вызывает функцию `callback`.
Пример использования:
```js
preloadImages(["1.jpg", "2.jpg", "3.jpg"], callback);
```
Если вдруг возникает ошибка при загрузке -- считаем такое изображение загруженным, чтобы не ломать поток выполнения.
Такая функция может полезна, например, для фоновой загрузки картинок в онлайн-галерею.
В исходном документе содержатся ссылки на картинки, а также код для проверки, действительно ли изображения загрузились. Он должен выводить "0", затем "300".

View file

@ -1,18 +0,0 @@
# Подсказка
Добавляйте `SCRIPT` при помощи методов `DOM`:
```js
var script = document.createElement('script');
script.src = src;
// в документе может не быть HEAD или BODY,
// но хотя бы один (текущий) SCRIPT в документе есть
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(script, s); // перед ним и вставим
```
На скрипт повесьте обработчики `onload/onreadystatechange`.
# Решение

View file

@ -1,3 +0,0 @@
function go() {
alert("ok");
}

View file

@ -1,42 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function addScript(src, callback) {
var script = document.createElement('script');
script.src = src;
var s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(script, s);
var loaded = false;
function onload() {
if (loaded) return; // повторный вызов
loaded = true;
callback();
}
script.onload = onload; // все браузеры, IE с версии 9
script.onreadystatechange = function() { // IE8-
if (this.readyState == 'loaded' || this.readyState == 'complete') {
setTimeout(onload, 0);
}
};
}
addScript("go.js", function() {
go();
});
</script>
</body>
</html>

View file

@ -1,3 +0,0 @@
function go() {
alert("ok");
}

View file

@ -1,22 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function addScript(src, callback) {
/* ваш код */
}
addScript("go.js", function() {
go();
});
</script>
</body>
</html>

View file

@ -1,21 +0,0 @@
importance: 4
---
# Скрипт с коллбэком
Создайте функцию `addScript(src, callback)`, которая загружает скрипт с данным `src`, и после его загрузки и выполнения вызывает функцию `callback`.
Скрипт может быть любым, работа функции не должна зависеть от его содержимого.
Пример использования:
```js
// go.js содержит функцию go()
addScript("go.js", function() {
go();
});
```
Ошибки загрузки обрабатывать не нужно.

View file

@ -1,8 +0,0 @@
# Подсказки
Создайте переменную-счетчик для подсчёта количества загруженных скриптов.
Чтобы один скрипт не учитывался два раза (например, `onreadystatechange` запустился при `loaded` и `complete`), учитывайте его состояние в объекте `loaded`. Свойство `loaded[i] = true` означает что `i`-й скрипт уже учтён.
# Решение

View file

@ -1,3 +0,0 @@
function a() {
b();
}

View file

@ -1,3 +0,0 @@
function b() {
c();
}

View file

@ -1,3 +0,0 @@
function c() {
alert('ok');
}

View file

@ -1,54 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
function addScript(src) {
var script = document.createElement('script');
script.src = src;
var s = document.getElementsByTagName('script')[0]
s.parentNode.insertBefore(script, s);
return script;
}
function addScripts(scripts, callback) {
var loaded = {}; // Для загруженных файлов loaded[i] = true
var counter = 0;
function onload(i) {
if (loaded[i]) return; // лишний вызов onload/onreadystatechange
loaded[i] = true;
counter++;
if (counter == scripts.length) callback();
}
for (var i = 0; i < scripts.length; i++)(function(i) {
var script = addScript(scripts[i]);
script.onload = function() {
onload(i);
};
script.onreadystatechange = function() { // IE8-
if (this.readyState == 'loaded' || this.readyState == 'complete') {
setTimeout(this.onload, 0); // возможны повторные вызовы onload
}
};
}(i));
}
addScripts(["a.js", "b.js", "c.js"], function() {
a()
});
</script>
</body>
</html>

View file

@ -1,3 +0,0 @@
function a() {
b();
}

View file

@ -1,3 +0,0 @@
function b() {
c();
}

View file

@ -1,3 +0,0 @@
function c() {
alert('ok');
}

View file

@ -1,21 +0,0 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script>
/* ваш код */
// функция a() сработает только если загружены a.js, b.js, c.js
addScripts(["a.js", "b.js", "c.js"], function() {
a()
});
</script>
</body>
</html>

View file

@ -1,22 +0,0 @@
importance: 5
---
# Скрипты с коллбэком
Создайте функцию `addScripts(scripts, callback)`, которая загружает скрипты из массива `scripts`, и *после загрузки и выполнения их всех* вызывает функцию `callback`.
Скрипт может быть любым, работа функции не должна зависеть от его содержимого.
Пример использования:
```js no-beautify
addScripts(["a.js", "b.js", "c.js"], function() { a() });
/* функция a() описана в a.js и использует b.js,c.js */
```
- Ошибки загрузки обрабатывать не нужно.</li>
- Один скрипт не ждёт другого. Они все загружаются, а по окончании вызывается обработчик `callback`.
Исходный код содержит скрипты `a.js`, `b.js`, `c.js`:

View file

@ -1,227 +1,89 @@
# 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` -- если загрузка успешна.
- `onerror` -- если при загрузке произошла ошибка.
- `onload` -- successful load,
- `onerror` -- an error occured.
## Загрузка SCRIPT
## 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
var script = document.createElement('script');
let script = document.createElement('script');
script.src = "my.js";
document.body.appendChild(script);
document.head.append(script);
```
...Но как после подгрузки выполнить функцию, которая объявлена в этом скрипте? Для этого нужно отловить момент окончания загрузки и выполнения тега `<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
Главным помощником станет событие `onload`. Оно сработает, когда скрипт загрузился и выполнился.
The main helper is the `load` event. It triggers after the script was loaded and executed.
Например:
For instance:
```js run
var script = document.createElement('script');
```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.body.appendChild(script);
document.head.append(script);
*!*
script.onload = function() {
// после выполнения скрипта становится доступна функция _
alert( _ ); // её код
}
// the script creates a helper function "_"
alert(_); // the function is available
};
*/!*
```
Это даёт возможность, как в примере выше, получить переменные из скрипта и выполнять объявленные в нём функции.
So in `onload` we can use script variables, run functions etc.
...А что, если загрузка скрипта не удалась? Например, такого скрипта на сервере нет (ошибка 404) или сервер "упал" (ошибка 500).
Такую ситуацию тоже нужно как-то обрабатывать, хотя бы сообщить посетителю о возникшей проблеме.
...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
Любые ошибки загрузки (но не выполнения) скрипта отслеживаются обработчиком `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
var script = document.createElement('script');
script.src = "https://example.com/404.js"
document.body.appendChild(script);
let script = document.createElement('script');
script.src = "https://example.com/404.js"; // no such script
document.head.append(script);
*!*
script.onerror = function() {
alert( "Ошибка: " + this.src );
alert("Error loading " + this.src); // Error loading https://example.com/404.js
};
*/!*
```
### IE8-: script.onreadystatechange [#onreadystatechange]
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.
Примеры выше работают во всех браузерах, кроме IE8-.
## Other resources
В IE для отслеживания загрузки есть другое событие: `onreadystatechange`. Оно срабатывает многократно, при каждом обновлении состояния загрузки.
The `load` and `error` events also work for other resources. There may be minor differences though.
Текущая стадия процесса находится в `script.readyState`:
For instance:
`loading`
: В процессе загрузки.
`loaded`
: Получен ответ с сервера -- скрипт или ошибка. Скрипт на фазе `loaded` может быть ещё не выполнен.
`complete`
: Скрипт выполнен.
Например, рабочий скрипт:
```js run no-beautify
var script = document.createElement('script');
script.src = "https://code.jquery.com/jquery.js";
document.documentElement.appendChild(script);
*!*
script.onreadystatechange = function() {
alert(this.readyState); // loading -> loaded -> complete
}
*/!*
```
Скрипт с ошибкой:
```js run no-beautify
var script = document.createElement('script');
script.src = "http://ajax.googleapis.com/404.js";
document.documentElement.appendChild(script);
*!*
script.onreadystatechange = function() {
alert(this.readyState); // loading -> loaded
}
*/!*
```
Обратим внимание на две особенности:
- **Стадии могут пропускаться.**
Если скрипт в кэше браузера -- он сразу даст `complete`. Вы можете увидеть это, если несколько раз запустите первый пример.
- **Нет особой стадии для ошибки.**
В примере выше это видно, обработка останавливается на `loaded`.
Итак, самое надёжное средство для IE8- поймать загрузку (или ошибку загрузки) -- это повесить обработчик на событие `onreadystatechange`, который будет срабатывать и на стадии `complete` и на стадии `loaded`. Так как скрипт может быть ещё не выполнен к этому моменту, то вызов функции лучше сделать через `setTimeout(.., 0)`.
Пример ниже вызывает `afterLoad` после загрузки скрипта и работает только в IE:
```js run no-beautify
var script = document.createElement('script');
script.src = "https://code.jquery.com/jquery.js";
document.documentElement.appendChild(script);
function afterLoad() {
alert("Загрузка завершена: " + typeof(jQuery));
}
*!*
script.onreadystatechange = function() {
if (this.readyState == "complete") { // на случай пропуска loaded
afterLoad(); // (2)
}
if (this.readyState == "loaded") {
setTimeout(afterLoad, 0); // (1)
// убираем обработчик, чтобы не сработал на complete
this.onreadystatechange = null;
}
}
*/!*
```
Вызов `(1)` выполнится при первой загрузке скрипта, а `(2)` -- при второй, когда он уже будет в кеше, и стадия станет сразу `complete`.
Функция `afterLoad` может и не обнаружить `jQuery`, если при загрузке была ошибка, причём не важно какая -- файл не найден или синтаксис скрипта ошибочен.
### Кросс-браузерное решение
Для кросс-браузерной обработки загрузки скрипта или её ошибки поставим обработчик на все три события: `onload`, `onerror`, `onreadystatechange`.
Пример ниже выполняет функцию `afterLoad` после загрузки скрипта *или* при ошибке.
Работает во всех браузерах:
```js run
var script = document.createElement('script');
script.src = "https://code.jquery.com/jquery.js";
document.documentElement.appendChild(script);
function afterLoad() {
alert( "Загрузка завершена: " + typeof(jQuery) );
}
script.onload = script.onerror = function() {
if (!this.executed) { // выполнится только один раз
this.executed = true;
afterLoad();
}
};
script.onreadystatechange = function() {
var self = this;
if (this.readyState == "complete" || this.readyState == "loaded") {
setTimeout(function() {
self.onload()
}, 0); // сохранить "this" для onload
}
};
```
## Загрузка других ресурсов
Поддержка этих событий для других типов ресурсов различна:
`<img>`, `<link>` (стили)
: Поддерживает `onload/onerror` во всех браузерах.
`<img>`, `<link>` (external stylesheets)
: Both `load` and `error` events work as expected.
`<iframe>`
: Поддерживает `onload` во всех браузерах. Это событие срабатывает как при успешной загрузке, так и при ошибке.
: 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.
Обратим внимание, что если `<iframe>` загружается с того же домена, то можно, используя `iframe.contentWindow.document` получить ссылку на документ и поставить обработчик `DOMContentLoaded`. А вот если `<iframe>` -- с другого домена, то так не получится, однако сработает `onload`.
## Summary
## Итого
Pictures `<img>`, external styles, scripts and other resources provide `load` and `error` events to track their loading:
В этой статье мы рассмотрели события `onload/onerror` для ресурсов.
- `load` triggers on a successful load,
- `error` triggers on a failed load.
Их можно обобщить, разделив на рецепты:
Отловить загрузку скрипта (включая ошибку)
: Ставим обработчики на `onload` + `onerror` + (для IE8-) `onreadystatechange`, как указано в рецепте выше
Отловить загрузку картинки `<img>` или стиля `<link>`
: Ставим обработчики на `onload` + `onerror`
```js no-beautify
var img = document.createElement('img');
img.onload = function() { alert("Успех "+this.src };
img.onerror = function() { alert("Ошибка "+this.src };
img.src = ...
```
Изображения начинают загружаться сразу при создании, не нужно их для этого вставлять в HTML.
**Чтобы работало в IE8-, `src` нужно ставить *после* `onload/onerror`.**
Отловить загрузку `<iframe>`
: Поддерживается только обработчик `onload`. Он сработает, когда `IFRAME` загрузится, со всеми подресурсами, а также в случае ошибки.
The only exception is `<iframe>`: for historical reasons it always triggers `load`, for any load completion, even if the page is not found.