На этом занятии
рассмотрим события, возникающие при загрузке HTML-документа и при
его обновлении в браузере. Они такие:
-
DOMContentLoaded
– браузер полностью загрузил HTML, было построено DOM-дерево, но внешние
ресурсы, такие как картинки (img) и стили (CSS), могут быть
ещё не загружены;
-
load
– браузер загрузил HTML и внешние ресурсы (картинки, стили и т.д.);
-
beforeunload/unload
– пользователь покидает страницу.
В каких случаях
используются эти события? Например, DOMContentLoaded полезно для инициализации
каких-либо интерфейсных функций, прописанных в скрипте. Событие load может
использоваться для динамической загрузки ресурсов (например, в браузерных играх
следует подождать, пока все картинки загрузятся). При возникновении события beforeunload
можно предупредить пользователя, что он не сохранил какие-либо изменения на
своей странице (это, в частности, можно встретить в youtube). Ну а на
событие unload можно повесить обработчик сбора статистики, чтобы определить
время, которое пользователь провел на странице.
Рассмотрим
работу этих событий подробнее и начнем с первого DOMContentLoaded. Возьмем вот
такой HTML-документ с отображением
рисунка:
<!DOCTYPE html>
<html>
<head>
<title>Уроки по JavaScript</title>
</head>
<body>
<p><img id="image" src="/htm/javascript_dom/files/images/winter.jpg?nocashe=1" />
<script>
</script>
</body>
</html>
И добавим обработчик для
DOMContentLoaded. Это
можно сделать только через метод addEventListener объекта document:
document.addEventListener("DOMContentLoaded", ready);
function ready(event) {
console.log("DOMContentLoaded");
console.log(`Изображение: ${image.offsetWidth}x${image.offsetHeight}`);
}
В обработчике мы
выводим размер изображения. Обновляем документ и видим, что размер нулевой. Это
как раз и говорит о том, что событие DOMContentLoaded возникает до загрузки
ресурсов (если, конечно, они не были закэшированы).
В целом, все
понятно и просто. Но, как всегда, есть один нюанс. Данное событие отрабатывает
только после выполнения всех скриптов, записанных в HTML-документе.
Например, если мы добавим вот такой скрипт в конец документа:
<script>
console.log("вызов метода write");
document.write('Добавляем тег p на страницу');
</script>
То сначала
сработает он, а потом уже событие DOMContentLoaded. Обновляем страницу и видим
этот эффект.
Почему
разработчики языка JavaScript реализовали такую последовательность
вызовов? Дело в том, что в момент загрузки страницы скрипты могут вносить
изменения в DOM-дерево: удалять
или добавлять какие-то элементы. Поэтому, нужно сначала полностью сформировать DOM, а значит,
выполнить скрипты, и только потом сгенерировать сообщение DOMContentLoaded. Так
что такое поведение браузера вполне логично.
Из этого правила
есть только два исключения:
-
Скрипты
с атрибутом async (который мы рассмотрим немного позже), не блокируют
DOMContentLoaded.
-
Скрипты,
сгенерированные динамически при помощи document.createElement('script') и затем
добавленные на страницу, также не блокируют это событие.
С каскадными
таблицами стилей (CSS) все несколько иначе. Изначально, событие DOMContentLoaded
возникает не дожидаясь загрузки стилей, так как они непосредственно не
затрагивают DOM-дерево. Однако,
если на странице есть скрипт, который использует CSS-стили,
например, такой:
<script>
// скрипт не выполняется, пока не загрузятся стили
console.log(getComputedStyle(document.body).marginTop);
</script>
То браузер будет
вынужден сгенерировать событие DOMContentLoaded после загрузки стилей. Вот эти
нюансы следует иметь в виду при работе с этим событием.
Следующее
событие load для объекта window работает очень
просто: оно генерируется браузером, когда HTML-документ
полностью загружен вместе со всеми связанными с ним ресурсами:
window.onload = function(event) {
console.log("load");
console.log(`Изображение: ${image.offsetWidth}x${image.offsetHeight}`);
}
Здесь при
обновлении документа мы увидим полные размеры изображения.
Следующее
событие unload объекта window выполняется
когда пользователь практически покинул HTML-страницу.
Обычно, в этот момент отправляется статистика поведения пользователя на
странице серверу:
window.addEventListener("unload", function() {
console.log("отправка данных на сервер");
});
Если мы теперь
станем обновлять станицу, то в консоли будет появляться данное сообщение. К
сожалению, продемонстрировать работу этого события как то приятнее не
получается. Поэтому, далее, пара слов теории. Для отправки данных серверу в
объекте navigator существует
такой специальный метод:
navigator.sendBeacon(url, data);
Здесь url – это путь к
скрипту на сервере, который будет принимать данные data. Особенность
этого метода в том, что при закрытии страницы нет необходимости дожидаться
окончания его работы. Браузер будет его выполнять в фоне даже при отсутствии
ранее открытого документа. Поэтому, если вы хотите что-то отправить на сервер
при закрытии HTML-страницы, то
это лучше всего делать через этот метод. На практике он реализуется примерно
так:
window.addEventListener("unload", function() {
navigator.sendBeacon("/analytics.php", JSON.stringify(myData));
};
И работает по
такой схеме:
-
данные
отправляются по POST-запросу;
-
размер
данных ограничен 64 Кб.
Также в этом
событии нельзя предупредить пользователя о каких-то несохраненных данных на
странице и прервать закрытие документа. Если нужно выполнить такие действия, то
для этого используется событие
beforeunload
объекта window следующим
образом:
window.onbeforeunload = function() {
return false;
};
Теперь, при
обновлении документа будет появляться окно, спрашивающее пользователя:
действительно ли он хочет перезагрузить страницу. Это должно привлечь его
внимание и напомнить о возможных последствиях такого действия, например, на
несохранение каких-то данных.
Ранее, в
браузерах можно было писать свои сообщения в таких окнах, примерно так:
window.onbeforeunload = function() {
return "Не уходи не попрощавшись!";
};
В этом случае
также будет появляться окно с предупреждением, но к с частью строка сообщения в
новых браузерах игнорируется и заменяется стандартным предупреждением, так как
программисты прошлого злоупотребляли этой возможностью.
readyState
В заключение
занятия отметим свойство
document.readyState
которое в момент
загрузки HTML-документа принимает
следующие значения:
-
"loading"
– документ в процессе загрузки;
-
"interactive"
– документ был полностью прочитан (парсинг документа завершен);
-
"complete"
– документ был полностью прочитан и все ресурсы (изображения, стили и т.п.)
тоже загружены.
В ряде случаев
это свойство бывает весьма полезно. Например, мы вызываем функцию, но не
уверены, что DOM-дерево
полностью построено. Поэтому, делаем такую проверку:
removeImage();
function removeImage() {
if(document.readyState == "loading") {
console.log("документ грузится, вешаем обработчик");
document.addEventListener("DOMContentLoaded", removeImage);
}
else {
console.log("удаляем изображение");
document.body.remove(image);
}
}
По аналогии
могут быть обработаны и остальные свойства.
Для полноты картины
пару слов о событии readystatechange, которое появилось до событий
DOMContentLoaded, load, unload, beforeunload
и в старых версиях
JavaScript процесс
загрузки документа контролировался через него. Например, так:
document.addEventListener('readystatechange', function() {
console.log(document.readyState);
});
Теперь при
обновлении страницы мы можем увидеть изменение состояний свойства document.readyState в процессе
загрузки. Однако такой механизм отслеживания ушел в прошлое и сейчас уже нет смысла
о нем подробно говорить.
Итак, на этом
занятии мы с вами рассмотрели события
DOMContentLoaded,
load, unload, beforeunload
и поговорили о свойстве
document.readyState
которое
дополняет работу с этими событиями.