Ожидание загрузки изображения в цикле для ротации изображений [дубликат]

93
19 ноября 2021, 11:00
На этот вопрос уже даны ответы здесь:
Как вернуть значение из события или из функции обратного вызова? Или хотя бы дождаться их окончания (3 ответа)
Закрыт 1 год назад.

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

Проблема заключается в том что мне не удаётся заставить браузер сначала загрузить изображение, а потом показать его, и только после этого начинат отсчёт таймаута. Проблема усугубляется что используется Lazy Load XT 1.1.0.

HTML изображения выглядит так:

<img src="URL" data-src="URL"
   data-images="изображение1|изображение2|изображение3|изображение4|изображение5|и т.д."
   data-timeout="опционально, по умолчанию 0.1">

Таких изображений множество, и у всех свои атрибуты, работать они должны соответственно так же независимо друг от друга.

  1. При наведении указателя на изображение начинаем загрузку первого изображения из data-images.
  2. После того как изображение успешно загружено, добавляем к img атрибут data-images_original с содержимым оригинального src и меняем img src на адрес полученного изображения. Если загрузка изображения завершилось неудачей, пробуем ещё 3 раза и переходим к следующему.
  3. Начинаем отсчёт таймера, после окончания времени таймера, повторяем пункт №2 и пункт №3 (рекурсия).
  4. Когда все изображения уже были показаны, начинаем показ с первого изображения из data-images по кругу.

Почему img.onload = function не дожидается исполнения кода внутри, и позволяет циклу исполняться дальше?

$("[data-images]").on('mouseover mouseout', function(data) { 
  if (data.type == "mouseover") { 
    $(this).attr("data-images_original", $(this).attr("src")); 
    images_data = $(this).attr("data-images"); 
    images = images_data.split('|'); 
    images_counter = images.length; 
    timeout = 1000; 
 
    for (for_counter = 1; for_counter < images_counter; for_counter++) { 
      image_url = images[while_counter]; 
      var img = new Image(); 
      img.onload = function() { 
        if (img.width == 0) {} else { 
          // $(this).attr("src", image_url); 
        } 
      }; 
      img.onerror = function() {}; 
      img.src = image_url; 
      $(this).attr("src", image_url); 
    } 
  } else if (data.type == "mouseout") { 
    $(this).attr("src", $(this).attr("data-images_original")); 
  } 
});

Answer 1

Почему img.onload = function не дожидается исполнения кода внутри, и позволяет циклу исполняться дальше?

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

100ms слишком быстро, невозможно там что-то разглядеть) А вам нужна функция, которая будет вызывать сама себя после onload:

let test = document.querySelectorAll('.test'); 
 
Array.from(test).forEach(function(img){ 
  let frameTimeout = null; 
   
  img.addEventListener('mouseenter', function(){ 
    let imgs = this.dataset.images.split("|");     
    let i = 0; 
     
    recursiveLoad(); 
     
    function recursiveLoad(){ 
      img.src = "https://gyazo.com/" + imgs[i] + '.png'; 
       
      img.onload = function(){ 
        i = (i == imgs.length - 1) ? 0 : i + 1; 
        frameTimeout = setTimeout(recursiveLoad, 1000); 
      } 
      // детали с onerror 
    } 
  }); 
   
  img.addEventListener('mouseleave', function(){ 
    clearTimeout(frameTimeout); 
    img.src = ""; // default.png 
  }); 
});
img { width: 150px; height: 150px; background-color: grey; }
<img class="test" data-images="f850be7e9fc91aa2c8cb844603ea1e10|c708f879e56f725318fafca74b9cbe7f|fe48322c44d1c214ee8d9ba49876a0da"> 
 
<img class="test" data-images="f850be7e9fc91aa2c8cb844603ea1e10|c708f879e56f725318fafca74b9cbe7f|fe48322c44d1c214ee8d9ba49876a0da">

Answer 2

Создавать кучу Image буферов в циклах - совсем не требуется, если не хотим скачивать вообще все сразу.
Так как курсор один, он может быть только над одной картинкой единовременно. Значит, нужен всего один буферный элемент для всех, и только один таймер должен быть активен.

Пример:

($ => { 
  $(document).ready(previews);  
 
  function previews() { 
    const buf = new Image();  
    buf.cancel = function () { this.onload = void(0); };  
    buf.load = function (src, cb) { 
      this.cancel();  
      this.onload = () => cb(src);  
      this.src = src;  
    };  
    const nextSrc = el => { 
      const images = $(el).data('images').split('|'),  
            curIdx = +$(el).data('idx'),  
            resetIdx = !isFinite(curIdx) || curIdx + 1 >= images.length,  
            nxtIdx = resetIdx ? 0 : curIdx + 1; 
      $(el).data('idx', nxtIdx);   
      return images[nxtIdx];  
    };  
    $('[data-images]').on('mouseover', function () { 
      buf.load(`https://picsum.photos/id/${nextSrc(this)}/1800`, src => { 
        this.src = src;  
        this.previewTimeout = setTimeout(() => { 
          $(this).trigger('mouseover');  
        }, (+$(this).data('timeout') || 0.1) * 1e3);  
      });  
    }).on('mouseout', function () { 
      clearTimeout(this.previewTimeout);  
      buf.cancel();  
    }).each(function () { 
      this.src = `https://picsum.photos/id/${nextSrc(this)}/180`;  
    });  
  } 
})(jQuery); 
img { width: 180px; height: 180px; background: #eee; }
<script src="https://code.jquery.com/jquery-3.1.0.js"></script> 
<img src="" data-src="URL" data-images="1|2|3|4|5" data-timeout="0.5"> 
<img src="" data-src="URL" data-images="6|7|8|9|10" data-timeout="0.5"> 
<img src="" data-src="URL" data-images="11|12|13|14|15" data-timeout1="0.5">

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

В реальном коде, помимо обработки ошибок, желательно: подгружать следующую превьюшку заранее (начиная сразу после переключения), и хранить массив ссылок из data-атрибута в объекте элемента (чтобы на каждом переключении не создавать этот массив заново).

READ ALSO
Прервать цикл дойдя до блока в dom

Прервать цикл дойдя до блока в dom

Помогите пожалуйста с идеей, есть верстка, в очень упрощенном виде так:

68
Что не так с this? [дубликат]

Что не так с this? [дубликат]

Никак не могу понять почему в строке присвоения thisid = 120 браузер ругается

73