Загрузка документа: DOMContentLoaded, load, beforeunload, unload

Процесс загрузки HTML-документа, условно, состоит из трёх стадий:

  • DOMContentLoaded – браузер полностью загрузил HTML и построил DOM-дерево.
  • load – браузер загрузил все ресурсы.
  • beforeunload/unload – уход со страницы.

Все эти стадии очень важны. На каждую можно повесить обработчик, чтобы совершить полезные действия:

  • DOMContentLoaded – означает, что все DOM-элементы разметки уже созданы, можно их искать, вешать обработчики, создавать интерфейс, но при этом, возможно, ещё не догрузились какие-то картинки или стили.
  • load – страница и все ресурсы загружены, используется редко, обычно нет нужды ждать этого момента.
  • beforeunload/unload – можно проверить, сохранил ли посетитель изменения, уточнить, действительно ли он хочет покинуть страницу.

Далее мы рассмотрим важные детали этих событий.

DOMContentLoaded

Событие DOMContentLoaded происходит на document и поддерживается во всех браузерах, кроме IE8-. Про поддержку аналогичного функционала в старых IE мы поговорим в конце главы.

Обработчик на него вешается только через addEventListener:

document.addEventListener("DOMContentLoaded", ready);

Пример:

<script>
  function ready() {
    alert( 'DOM готов' );
    alert( "Размеры картинки: " + img.offsetWidth + "x" + img.offsetHeight );
  }

  document.addEventListener("DOMContentLoaded", ready);
</script>

<img id="img" src="https://js.cx/clipart/yozhik.jpg?speed=1">

В примере выше обработчик DOMContentLoaded сработает сразу после загрузки документа, не дожидаясь получения картинки.

Поэтому на момент вывода alert и сама картинка будет невидна и её размеры – неизвестны (кроме случая, когда картинка взята из кеша браузера).

В своей сути, событие onDOMContentLoaded – простое, как пробка. Полностью создано DOM-дерево – и вот событие. Но с ним связан ряд существенных тонкостей.

DOMContentLoaded и скрипты

Если в документе есть теги <script>, то браузер обязан их выполнить до того, как построит DOM. Поэтому событие DOMContentLoaded ждёт загрузки и выполнения таких скриптов.

Исключением являются скрипты с атрибутами async и defer, которые подгружаются асинхронно.

Побочный эффект: если на странице подключается скрипт с внешнего ресурса (к примеру, реклама), и он тормозит, то событие DOMContentLoaded и связанные с ним действия могут сильно задержаться.

Современные системы рекламы используют атрибут async, либо вставляют скрипты через DOM: document.createElement('script')..., что работает так же как async: такой скрипт выполняется полностью независимо от страницы и от других скриптов – сам ничего не ждёт и ничего не блокирует.

DOMContentLoaded и стили

Внешние стили никак не влияют на событие DOMContentLoaded. Но есть один нюанс.

Если после стиля идёт скрипт, то этот скрипт обязан дождаться, пока стиль загрузится:

<link type="text/css" rel="stylesheet" href="style.css">
<script>
  // сработает после загрузки style.css
</script>

Такое поведение прописано в стандарте. Его причина – скрипт может захотеть получить информацию со страницы, зависящую от стилей, например, ширину элемента, и поэтому обязан дождаться загрузки style.css.

Побочный эффект – так как событие DOMContentLoaded будет ждать выполнения скрипта, то оно подождёт и загрузки стилей, которые идут перед <script>.

Автозаполнение

Firefox/Chrome/Opera автозаполняют формы по DOMContentLoaded.

Это означает, что если на странице есть форма для ввода логина-пароля, то браузер введёт в неё запомненные значения только по DOMContentLoaded.

Побочный эффект: если DOMContentLoaded ожидает множества скриптов и стилей, то автозаполнение не сработает до полной их загрузки.

Конечно, это довод в пользу того, чтобы не задерживать DOMContentLoaded, в частности – использовать у скриптов атрибуты async и defer.

window.onload

Событие onload на window срабатывает, когда загружается вся страница, включая ресурсы на ней – стили, картинки, ифреймы и т.п.

Пример ниже выведет alert лишь после полной загрузки окна, включая IFRAME и картинку:

<script>
  window.onload = function() {
    alert( 'Документ и все ресурсы загружены' );
  };
</script>
<iframe src="https://example.com/" style="height:60px"></iframe>
<img src="https://js.cx/clipart/yozhik.jpg?speed=1">

window.onunload

Когда человек уходит со страницы или закрывает окно, на window срабатывает событие unload. В нём можно сделать что-то, не требующее ожидания, например, закрыть вспомогательные popup-окна, но отменить сам переход нельзя.

Это позволяет другое событие – onbeforeunload, которое поэтому используется гораздо чаще.

window.onbeforeunload

Если посетитель инициировал переход на другую страницу или нажал «закрыть окно», то обработчик onbeforeunload может приостановить процесс и спросить подтверждение.

Для этого ему нужно вернуть строку. По историческим причинам некоторые браузеры покажут ее, но большинство – стандартное сообщение.

Например:

window.onbeforeunload = function() {
  return "Данные не сохранены. Точно перейти?";
};

Кликните на кнопку в IFRAME'е ниже, чтобы поставить обработчик, а затем по ссылке, чтобы увидеть его в действии:

Эмуляция DOMContentLoaded для IE8-

Прежде чем что-то эмулировать, заметим, что альтернативой событию onDOMContentLoaded является вызов функции init из скрипта в самом конце BODY, когда основная часть DOM уже готова:

<body>
  ...
  <script>
    init();
  </script>
</body>

Причина, по которой обычно предпочитают именно событие – одна: удобство. Вешается обработчик и не надо ничего писать в конец BODY.

Мини-скрипт documentReady

Если вы всё же хотите использовать onDOMContentLoaded кросс-браузерно, то нужно либо подключить какой-нибудь фреймворк – почти все предоставляют такой функционал, либо использовать функцию из мини-библиотеки jquery.documentReady.js.

Несмотря на то, что в названии содержится слово «jquery», эта библиотечка не требует jQuery. Наоборот, она представляет собой единственную функцию с названием $, вызов которой $(callback) добавляет обработчик callback на DOMContentLoaded (можно вызывать много раз), либо, если документ уже загружен – выполняет его тут же.

Пример использования:

<script src="https://js.cx/script/jquery.documentReady.js"></script>

<script>
  $(function() {
    alert( "DOMContentLoaded" );
  });
</script>

<img src="https://js.cx/clipart/yozhik.jpg?speed=1">
<div>Текст страницы</div>

Здесь alert сработает до загрузки картинки, но после создания DOM, в частности, после появления текста. И так будет для всех браузеров, включая даже очень старые IE.

Как именно эмулируется DOMContentLoaded?

Технически, эмуляция DOMContentLoaded для старых IE осуществляется очень забавно.

Основной приём – это попытка прокрутить документ вызовом:

document.documentElement.doScroll("left");

Метод doScroll работает только в IE и «методом тыка» было обнаружено, что он бросает исключение, если DOM не полностью создан.

Поэтому библиотека пытается вызвать прокрутку, если не получается – через setTimeout(.., 1) пытается прокрутить его ещё раз, и так до тех пор, пока действие не перестанет вызывать ошибку. На этом этапе документ считается загрузившимся.

Внутри фреймов и в очень старых браузерах такой подход может ошибаться, поэтому дополнительно ставится резервный обработчик на onload, чтобы уж точно сработал.

Итого

  • Самое востребованное событие из описанных – без сомнения, DOMContentLoaded. Многие страницы сделаны так, что инициализируют интерфейсы именно по этому событию.

    Это удобно, ведь можно в <head> написать скрипт, который будет запущен в момент, когда все DOM-элементы доступны.

    С другой стороны, следует иметь в виду, что событие DOMContentLoaded будет ждать не только, собственно, HTML-страницу, но и внешние скрипты, подключенные тегом <script> без атрибутов defer/async, а также стили перед такими скриптами.

    Событие DOMContentLoaded не поддерживается в IE8-, но почти все фреймворки умеют его эмулировать. Если нужна отдельная функция только для кросс-браузерного аналога DOMContentLoaded – можно использовать jquery.documentReady.js.

  • Событие window.onload используют редко, поскольку обычно нет нужды ждать подгрузки всех ресурсов. Если же нужен конкретный ресурс (картинка или ифрейм), то можно поставить событие onload непосредственно на нём, мы посмотрим, как это сделать, далее.

  • Событие window.onunload почти не используется, как правило, оно бесполезно – мало что можно сделать, зная, что окно браузера прямо сейчас закроется.

  • Гораздо чаще применяется window.onbeforeunload – это де-факто стандарт для того, чтобы проверить, сохранил ли посетитель данные, действительно ли он хочет покинуть страницу. В системах редактирования документов оно используется повсеместно.

Карта учебника

Комментарии

перед тем как писать…
  • Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.