Нередко бывает, что при загрузке веб-страницы какие-то изображения оказываются недоступны, и браузер услужливо рисует на их месте пустые контуры. В этой статье я расскажу, как обработать ошибки загрузки изображений и подставить вместо них другое изображение.
Допустим, у нас на сервере лежит файл: 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>
 
