Браузер позволяет отслеживать загрузку внешних ресурсов — скриптов, ифреймов, картинок и других.
Для этого есть два события:
onload— если загрузка успешна.onerror— если при загрузке произошла ошибка.
Кроме того, обработчик window.onerror вызывается при ошибках JavaScript, которые не были пойманы try..catch.
Загрузка SCRIPT
Рассмотрим задачу. В браузере работает сложный интерфейс и, чтобы создать очередной компонент, нужно загрузить его скрипт с сервера.
Подгрузить внешний скрипт — достаточно просто:
var script = document.createElement('script');
script.src = "my.js";
document.documentElement.appendChild(script);
…Но, после подгрузки нужно запустить функцию создания компонента. Как определить, что скрипт загрузился?
onload
Здесь нам и поможет событие onload. Оно сработает, когда скрипт загрузился и выполнился.
var script = document.createElement('script');
script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.js"
document.documentElement.appendChild(script);
*!*
script.onload = function() {
alert(jQuery);
}
*/!*
..А что, если загрузка скрипта не удалась? Например, такого скрипта на сервере нет (ошибка 404).
onerror
Любые ошибки загрузки (но не выполнения) скрипта отслеживаются обработчиком onerror:
var script = document.createElement('script');
script.src = "http://ajax.googleapis.com/404.js"
document.documentElement.appendChild(script);
*!*
script.onerror = function() {
alert("Ошибка: " + this.src);
}
*/!*
IE<9: onreadystatechange
Примеры выше работают во всех браузерах, кроме IE<9.
В IE для отслеживания загрузки есть другое событие: onreadystatechange. Оно срабатывает многократно, при каждом обновлении состояния загрузки.
Текущая стадия процесса находится в script.readyState:
loading- В процессе загрузки.
loaded- Получен ответ с сервера — скрипт или ошибка. Скрипт на фазе
loadedможет быть ещё не выполнен. complete- Скрипт выполнен.
Например, рабочий скрипт:
var script = document.createElement('script');
script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.js";
document.documentElement.appendChild(script);
*!*
script.onreadystatechange = function() {
alert(this.readyState); // loading -> loaded -> complete
}
*/!*
Скрипт с ошибкой:
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.
Итак, самое надёжное средство для IE<9 поймать загрузку (и ошибку) — это повесить обработчик на событие loaded. Так как скрипт может быть ещё не выполнен к этому моменту, то вызов функции лучше сделать через setTimeout(.., 0).
На тот случай, если скрипт прыгнет сразу на complete — поставим обработчик и на этой стадии тоже.
Пример вызывает afterLoad после загрузки скрипта. Работает только в IE:
var script = document.createElement('script');
script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1/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 после загрузки скрипта.
Работает везде:
var script = document.createElement('script');
script.src = "http://ajax.googleapis.com/ajax/libs/jquery/1/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
}
};
Обработчики на window
window.onerror
Обработчик window.onerror вызывается в случае ошибки, выпавшей из всех блоков try..catch:
Аргументы обработчика:
message- Сообщение об ошибке.
source- Файл
lineno- Номер строки с ошибкой
Например:
<script>
window.onerror = function(message, source, lineno) {
alert("Ошибка:"+message +"\n" +
"файл:" + source + "\n" +
"строка:" + lineno);
};
</script>
<body>
<button onclick="alert(blablabla);">alert(blablabla)</button>
</body>
Этот документ в ифрейме:Открыть в новом окне
Реакция браузера на ошибку (запуск отладчика?) может быть отменена через return false из обработчика.
window.onload
Обработчик window.onload срабатывает, когда загружается вся страница, включая ресурсы на ней — стили, картинки, ифреймы и т.п.
Пример ниже выведет alert лишь после полной загрузки окна, включая IFRAME, стиль, картинки:
<iframe src="http://example.com/" style="height:60px"></iframe>
<link type="text/css" rel="stylesheet" href="/some.css">
<img src="/some.png">
<script>
*!*
window.onload = function() {
alert('Документ и все ресурсы загружены')
}
*/!*
</script>
window.onunload
Когда человек уходит со страницы или закрывает окно, срабатывает window.unload. В нём можно, например, закрыть вспомогательные popup-окна, но отменить сам переход нельзя.
Это позволяет другое событие — window.onbeforeunload.
window.onbeforeunload
Если посетитель инициировал переход на другую страницу или нажал «закрыть окно», то обработчик onbeforeunload может приостановить процесс и спросить подтверждение.
Для этого ему нужно вернуть строку, которую браузеры покажут посетителю, спрашивая — нужно ли переходить. Например:
window.onbeforeunload = function() {
return "Данные не сохранены. Точно перейти?";
};
Особенности:
- Событие не поддерживается Opera
- Firefox игнорирует текст, а показывает своё сообщение. Это сделано в целях безопасности.
Кликните на кнопку в IFRAME'е ниже, чтобы поставить обработчик, а затем по ссылке, чтобы увидеть его в действии:
Обработчики на IMG, IFRAME, LINK
Поддержка событий для других типов ресурсов:
IMG- Пддерживают
onload/onerrorво всех браузерах. IFRAME- Поддерживает
onloadво всех браузерах. Это событие срабатывает как при успешной загрузке, так и при ошибке. LINK(стили)- Поддерживает
onload/onerrorв некоторых браузерах. Замечательная статья про обработчик на загрузку стиля: When is a styleshet really loaded?.
Итого
В этой статье мы рассмотрели события onload/onerror и window.onbeforeunload.
Их можно обобщить, разделив на рецепты:
- Отловить загрузку скрипта (включая ошибку)
- Ставим обработчики на
onload+onerror+ (для IE<9)onreadystatechange, как указано в рецепте выше - Отловить загрузку изображения (включая ошибку)
- Ставим обработчики на
onload+onerror
var img = document.createElement('img'); img.onload = function() { alert("OK "+this.src }; img.onerror = function() { alert("FAIL "+this.src } img.src = ...Изображения начинают загружаться сразу при создании, не нужно их для этого вставлять в HTML.Чтобы работало в IE<9,
srcнужно ставить послеonload/onerror.
- Отловить загрузку
IFRAME - Поддерживается только обработчик
onload. Он сработает, когдаIFRAMEзагрузится, со всеми подресурсами, а также в случае ошибки.<iframe src="http://example.com/" style="height:60px"></iframe> <script> *!* document.getElementsByTagName('iframe')[0].onload = function() { alert('IFRAME загружен') } */!* </script> - Отловить полную загрузку страницы
- Обработчик
window.onload(сработает, когда окно загрузится со всеми ресурсами).
Далее мы рассмотрим событиеonDOMContentLoaded, которое срабатывает при загрузке документа и не ждёт картинок, стилей, ифреймов. - Отловить загрузку файла со стилями
- When is a styleshet really loaded?.
Обычно, до того как изображение загрузится (или при отключенных картинках), посетитель видит пустое место с текстом из «ALT». Но этот атрибут не допускает HTML-форматирования.
При мобильном доступе скорость небольшая, и хочется, чтобы посетитель сразу видел красивый текст.
Реализуйте «красивый» (HTML) аналог alt при помощи CSS/JavaScript, который затем будет заменён картинкой сразу же как только она загрузится. А если загрузка не состоится — то не заменён.
Демо: (нажмите «перезагрузить», чтобы увидеть процесс загрузки и замены)
Картинки для bing специально нет, так что текст остается «как есть».
Исходный документ: tutorial/browser/events/img-onload-src/index.html содержит разметку текста и изображения.
Текст на странице пусть будет изначально DIV, с классом img-replace и атрибутом data-src для картинки.
Функция replaceImg() должна искать такие DIV и загружать изображение с указанным src. По onload осуществляется замена DIV на картинку.
Создайте функцию preloadImages(sources, callback), которая предзагружает изображиния из массива sources, и после загрузки вызывает функцию callback.
Пример использования:
addScripts(["1.jpg", "2.jpg", "3.jpg"], callback);
Если вдруг возникает ошибка при загрузке — считаем такое изображение загруженным, чтобы не ломать поток выполнения.
Такая функция может полезна, например, для фоновой загрузки картинок в онлайн-галерею.
Исходный документ с картинками: tutorial/browser/events/images-load-src/index.html.
Там также содержится код для проверки, действительно ли изображения загрузились. Он должен выводить «0», затем «300».
Создайте переменную-счетчик для подсчёта количества загруженных картинок, и увеличивайте при каждом onload/onerror.
Когда счетчик станет равен количеству картинок — вызывайте callback.
Создайте функцию addScript(src, callback), которая загружает скрипт с данным src, и после его загрузки и выполнения вызывает функцию callback.
Скрипт может быть любым, работа функции не должна зависеть от его содержимого.
Пример использования:
// go.js содержит функцию go()
addScript("go.js", function() {
go();
});
Ошибки загрузки обрабатывать не нужно.
Исходный документ: tutorial/browser/events/script-load-src/index.html.
Добавляйте SCRIPT при помощи методов DOM:
var script = document.createElement('script');
script.src = src;
// в документе может не быть HEAD или BODY,
// но хотя бы один (текущий) SCRIPT в документе есть
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(script, s); // перед ним и вставим
На скрипт повесьте обработчики onload/onreadystatechange.
Создайте функцию addScripts(scripts, callback), которая загружает скрипты из массива scripts, и после загрузки и выполнения их всех вызывает функцию callback.
Скрипт может быть любым, работа функции не должна зависеть от его содержимого.
Пример использования:
addScripts(["a.js", "b.js", "c.js"], function() { a() });
/* функция a() описана в a.js и использует b.js,c.js */
- Ошибки загрузки обрабатывать не нужно.
- Один скрипт не ждёт другого. Они все загружаются, а по окончании вызывается обработчик
callback.
Исходный документ со скриптами a.js, b.js, c.js: tutorial/browser/events/scripts-load-src/index.html.
Создайте переменную-счетчик для подсчёта количества загруженных скриптов.
Чтобы один скрипт не учитывался два раза (например, onreadystatechange запустился при loaded и complete), учитывайте его состояние в объекте loaded. Свойство loaded[i] = true означает что i-й скрипт уже учтён.
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.