This commit is contained in:
Ilya Kantor 2015-11-18 16:15:29 +03:00
parent 4bca225593
commit 547854a151
1655 changed files with 847 additions and 89231 deletions

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

View file

@ -1,8 +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,23 +0,0 @@
# Загрузить изображения с коллбэком
[importance 4]
Создайте функцию `preloadImages(sources, callback)`, которая предзагружает изображения из массива `sources`, и после загрузки вызывает функцию `callback`.
Пример использования:
```js
addScripts(["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,19 +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,24 +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 */
```
<ul>
<li>Ошибки загрузки обрабатывать не нужно.</li>
<li>Один скрипт не ждёт другого. Они все загружаются, а по окончании вызывается обработчик `callback`.</li>
Исходный содержит скрипты `a.js`, `b.js`, `c.js`:

View file

@ -1,241 +0,0 @@
# Загрузка скриптов, картинок, фреймов: onload и onerror
Браузер позволяет отслеживать загрузку внешних ресурсов -- скриптов, ифреймов, картинок и других.
Для этого есть два события:
<ul>
<li>`onload` -- если загрузка успешна.</li>
<li>`onerror` -- если при загрузке произошла ошибка.</li>
</ul>
## Загрузка SCRIPT
Рассмотрим следующую задачу.
В браузере работает сложный интерфейс и, чтобы создать очередной компонент, нужно загрузить скрипт с сервера.
Подгрузить внешний скрипт -- достаточно просто:
```js
var script = document.createElement('script');
script.src = "my.js";
document.body.appendChild(script);
```
...Но как после подгрузки выполнить функцию, которая объявлена в этом скрипте? Для этого нужно отловить момент окончания загрузки и выполнения тега `<script>`.
### script.onload
Главным помощником станет событие `onload`. Оно сработает, когда скрипт загрузился и выполнился.
Например:
```js
//+ run
var script = document.createElement('script');
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js"
document.body.appendChild(script);
*!*
script.onload = function() {
// после выполнения скрипта становится доступна функция _
alert( _ ); // её код
}
*/!*
```
Это даёт возможность, как в примере выше, получить переменные из скрипта и выполнять объявленные в нём функции.
...А что, если загрузка скрипта не удалась? Например, такого скрипта на сервере нет (ошибка 404) или сервер "упал" (ошибка 500).
Такую ситуацию тоже нужно как-то обрабатывать, хотя бы сообщить посетителю о возникшей проблеме.
### script.onerror
Любые ошибки загрузки (но не выполнения) скрипта отслеживаются обработчиком `onerror`.
Например, сделаем запрос заведомо отсутствующего скрипта:
```js
//+ run
var script = document.createElement('script');
script.src = "https://example.com/404.js"
document.body.appendChild(script);
*!*
script.onerror = function() {
alert( "Ошибка: " + this.src );
};
*/!*
```
### IE8-: script.onreadystatechange [#onreadystatechange]
Примеры выше работают во всех браузерах, кроме IE8-.
В IE для отслеживания загрузки есть другое событие: `onreadystatechange`. Оно срабатывает многократно, при каждом обновлении состояния загрузки.
Текущая стадия процесса находится в `script.readyState`:
<dl>
<dt>`loading`</dt>
<dd>В процессе загрузки.</dd>
<dt>`loaded`</dt>
<dd>Получен ответ с сервера -- скрипт или ошибка. Скрипт на фазе `loaded` может быть ещё не выполнен.</dd>
<dt>`complete`</dt>
<dd>Скрипт выполнен.</dd>
</dl>
Например, рабочий скрипт:
```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
}
*/!*
```
Обратим внимание на две особенности:
<ul>
<li>**Стадии могут пропускаться.**
Если скрипт в кэше браузера -- он сразу даст `complete`. Вы можете увидеть это, если несколько раз запустите первый пример.</li>
<li>**Нет особой стадии для ошибки.**
В примере выше это видно, обработка останавливается на `loaded`.
</li>
</ul>
Итак, самое надёжное средство для 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
}
};
```
## Загрузка других ресурсов
Поддержка этих событий для других типов ресурсов различна:
<dl>
<dt>`<img>`, `<link>` (стили)</dt>
<dd>Поддерживает `onload/onerror` во всех браузерах.</dd>
<dt>`<iframe>`</dt>
<dd>Поддерживает `onload` во всех браузерах. Это событие срабатывает как при успешной загрузке, так и при ошибке.
Обратим внимание, что если `<iframe>` загружается с того же домена, то можно, используя `iframe.contentWindow.document` получить ссылку на документ и поставить обработчик `DOMContentLoaded`. А вот если `<iframe>` -- с другого домена, то так не получится, однако сработает `onload`.
</dd>
</dl>
## Итого
В этой статье мы рассмотрели события `onload/onerror` для ресурсов.
Их можно обобщить, разделив на рецепты:
<dl>
<dt>Отловить загрузку скрипта (включая ошибку)</dt>
<dd>Ставим обработчики на `onload` + `onerror` + (для IE8-) `onreadystatechange`, как указано в рецепте выше</dd>
<dt>Отловить загрузку картинки `<img>` или стиля `<link>`</dt>
<dd>Ставим обработчики на `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`.**
</dd>
<dt>Отловить загрузку `<iframe>`</dt>
<dd>Поддерживается только обработчик `onload`. Он сработает, когда `IFRAME` загрузится, со всеми подресурсами, а также в случае ошибки.
</dd>
</dl>