null

Обработка ошибок загрузки изображений: событие onerror

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

Допустим, у нас на сервере лежит файл: https://svgshare.com/i/7ck.svg

Рассмотрим следующий пример. Сколько изображений будет показано?

<html>
  <head>
    <meta charset="utf-8" content="text/html; charset=utf-8"/>
    <style>
      html, body {
        height: 100px;
        width: auto;
      }
    </style>
  </head>

  <body>
    <div class="achievement-image">
      <img src="https://svgshare.com/i/7ck.svg">
      <img src="//svgshare.com/i/7ck.svg">
      <img src="svgshare.com/i/7ck.svg">
      <img src="7ck.svg">
    </div>
  </body>
</html>


Одно, максимум два.

Ответ зависит от того, как открыть этот код. Если разместить его на сервере, вроде https://myserver.com, мы увидим первые два изображения. Если же сохранить его локально и открыть в браузере html файл - только одно. Для ясности я покажу, во что превращаются ссылки из атрибута src в обоих случаях.

В случае сервера https://myserver.com имеем:

https://svgshare.com/i/7ck.svg
https://svgshare.com/i/7ck.svg
https://myserver.com/svgshare.com/i/7ck.svg
https://myserver.com/7ck.svg

В случае локального файла из file:///home/leksa/html/example.html имеем:

https://svgshare.com/i/7ck.svg
file://svgshare.com/i/7ck.svg
file:///home/leksa/html/svgshare.com/i/7ck.svg
file:///home/leksa/html/7ck.svg

Для отслеживания и реакции на успешную или неуспешную загрузку изображения HTML предлагает нам два события, доступных для тега img: onload и onerror. Как следует из названия, первое событие возникает, когда изображение было загружено, а второе, когда что-то пошло не так.

Модифицируем наш код и проверим работу этих событий в консоли браузера.

<div class="achievement-image">
  <img src="https://svgshare.com/i/7ck.svg" 
       onload="console.log('load 1')" onerror="console.log('error 1')">
  <img src="//svgshare.com/i/7ck.svg"
       onload="console.log('load 2')" onerror="console.log('error 2')">
  <img src="svgshare.com/i/7ck.svg"
       onload="console.log('load 3')" onerror="console.log('error 3')">
  <img src="7ck.svg"
       onload="console.log('load 4')" onerror="console.log('error 4')">
</div>

 

Как и ожидалось, последние три изображения спровоцировали событие onerror. Назначим на него свой обработчик (потребуется подключить библиотеку JQuery) и подменим атрибут src на адрес желаемого изображения.

$(document).ready(function() {
  $(".image-container img").each(function(key, item) {
    $(item).on("error", function() {
      showDefaultImage(this);
    }).attr('src', $(item).attr('src'));
  });
});

function showDefaultImage(img) {
  $(img).attr('src', 'https://svgshare.com/i/7bo.svg');
  $(img).off("error");
}

 

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

$(item).on(...).attr('src', $(item).attr('src'))

после назначения обработчика. Атрибут src устанавливается в то же значение, в котором он изначально был. Зачем это нужно?

Для ответа на этот вопрос вернёмся к последовательности возникновения событий.

  • Событие DOMContentLoaded возникает, когда построено дерево DOM, а также загружены и выполнены все скрипты. При этом внешние ресурсы, включая изображения и стили, могут быть ещё не загружены.
  • Событие load объекта window возникает, когда страница со всеми ресурсами была загружена.
  • Событие ready специфично для JQuery, согласно документации, оно аналогично событию DOMContentLoaded, то есть не дожидается загрузки мультимедийного контента.

Откроем код, приведённый ниже, и посмотрим на вывод в консоли браузера.

<html>
  <head>
    <meta charset="utf-8" content="text/html; charset=utf-8"/>

    <style>
      html, body {
        height: 100px;
        width: auto;
      }
    </style>

    <script>
      $(document).on("DOMContentLoaded", function() {
        console.log("document loaded");
      });
      $(window).on("load", function() {
        console.log("content loaded");
      });
      $(document).ready(function() {
        console.log(“JQuery ready”);
      });
    </script>
  </head>

  <body>
    <div class="image-container">
      <img src="https://svgshare.com/i/7ck.svg" 
           onload="console.log('load 1')" onerror="console.log('error 1')">
      <img src="//svgshare.com/i/7ck.svg"
           onload="console.log('load 2')" onerror="console.log('error 2')">
      <img src="svgshare.com/i/7ck.svg"
           onload="console.log('load 3')" onerror="console.log('error 3')">
      <img src="7ck.svg"
           onload="console.log('load 4')" onerror="console.log('error 4')">
    </div>
  </body>
</html>

 


 

Тут есть кое-что интересное. Вопреки ожиданием, событие ready срабатывает после загрузки всего контента (включая изображения), аналогично событию load. А значит, на момент создания обработчиков, события error и load уже произошли, и обработчики никогда не будут вызваны.

В браузере firefox картина примерно такая же.

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

Окончательный вариант кода приведён ниже (живой пример).
 

<html>
  <head>
    <meta charset="utf-8" content="text/html; charset=utf-8"/>

    <style>
      html, body {
        height: 100px;
        width: auto;
      }
    </style>

    <script>
      // $(document).on("DOMContentLoaded", function() {
      $(document).ready(function() {
        $(".image-container img").each(function(key, item) {
          $(item).on("error", function() {
            showDefaultImage(this);
          }).attr('src', $(item).attr('src'));
        });
      });

      function showDefaultImage(img) {
        $(img).attr('src', 'https://svgshare.com/i/7bo.svg');
        $(img).off("error");
      }
    </script>
  </head>

  <body>
    <div class="image-container">
      <img src="https://svgshare.com/i/7ck.svg">
      <img src="//svgshare.com/i/7ck.svg">
      <img src="svgshare.com/i/7ck.svg">
      <img src="7ck.svg">
    </div>
  </body>
</html>