Предварительная загрузка изображений Asyn c с помощью Promise.all не работает в браузерах, отличных от Chrome - PullRequest
1 голос
/ 03 мая 2020
const preloadImg = (...urls) => {
    const toolDiv = document.createElement('div');
    toolDiv.style = 'display: none';

    const load = url => {
        return new Promise(res => {
            const img = new Image();
            img.src = url;
            img.onload = () => res(img);
        });
    };
    const getImgs = imgs => {
        const promises = imgs.map(async url => {
            const img = await load(url);
            toolDiv.appendChild(img);
        });
        return Promise.all(promises);
    }
    getImgs(urls).then(() => {
        document.body.appendChild(toolDiv);
    });
};

Вы видите <div>, содержащий все эти изображения в Elements, и пути верны:

<div style="display: none"> // I try removing <display: none> but not working, either
<img src="img/329b774421235a3b27d7142b1707ea01.jpg">
<img src="img/33df62feaa1871d7ff4c2b933aa82992.jpg">
<img src="img/5681a01b46e89618d96ff523dc81a1fb.jpg">
<img src="img/183ad681c899a84c82e288ac8ad30604.jpg">
<img src="img/1d9c8fdb875c31cbfa1f83e11a7038af.jpg">
<img src="img/71b1abb6a445059bf43463ab80e75506.jpg">
<img src="img/40734ae2e8255713e76814eba786f018.jpg">
<img src="img/8c40e3bdea0a863b76d888ad9952cf74.jpg">
<img src="img/8aa56b4e08ef9a40c92e6e0609991280.jpg">
</div>

Также в Network вы можете видеть, что все запросы были выбран правильно (скриншот с Edge 18, где предварительная загрузка не работает):

screenshot from Edge, where preloading is not working


Предварительная загрузка asyn c работает только в Chrome (и Opera) любые другие браузеры (ff, edge, ie) приводят к мерцанию, как будто они не загружены вообще (но при отображении нет никакого дополнительного http-запроса, что означает, что эти изображения были извлечены и кэшированы) .

Я проверяю поддержку браузером Promise, Promise.all и async await и никаких проблем.

Однако традиционная синхронная предварительная загрузка отлично работает во всех браузерах:

    urls.forEach(url => {
        const img = new Image();
        img.src = url;
        toolDiv.appendChild(img);
    });
    document.body.appendChild(toolDiv);

Эти изображения были обработаны загрузчиком файлов Webpack с помощью Babel (core- js & runtime), но я думаю, что это не проблема.

Нужна помощь, спасибо! !


Обновление: я просто одолжил у моего соседа по комнате iPhone 8 и не мерцал.



Я сделаю это яснее Вот. Эти изображения отображаются в качестве фоновых изображений, переключающихся по классам css и запускаемых колесом мыши, например:

/*.css */
.img1::before,
.img1::after {
    background-image: url(../img/img1.jpg); /* Would be ../img/dasdfsafadasdasda.jpg after file-loader */
}
.img2::before,
.img2::after {
    background-image: url(../img/img2.jpg);
}
<!-- .html -->
<aside class="img1" id="aside"></aside>
// .js
something.onwheel = () => {
    document.getElementById('aside').className = 'img2';
};

Переключение изображений по переименованию классов.

Но я все еще думаю, что этот переключатель классов тоже не проблема.



Окончательное обновление: метод @ Kaiido работает. Эта проблема вызвана частичной доступностью извлеченных изображений. Для полной доступности требуется следующий процесс декодирования. WHATWG

Ответы [ 2 ]

2 голосов
/ 03 мая 2020

Здесь вы видите, что событие load только сообщает нам, что ресурс был выбран и браузер может обрабатывать мультимедиа.
Большой шаг еще остается: Декодирование изображения .

Действительно, даже если весь ресурс был извлечен с сервера и что браузер может проанализировать заголовки файла, он сможет его декодировать и другие данные, такие как размер мультимедиа, некоторые браузеры будут ждать, пока они действительно потребуются, прежде чем пытаться фактически декодировать данные изображения («пиксели», если хотите).
Этот процесс все еще занимает время, и это не так прежде чем вы действительно прикрепите все эти элементы imageк документу, эти браузеры начнут выполнять эту операцию, отсюда и мерцание.

Теперь, почему Chrome не сталкивается с этой проблемой? Потому что, как показано в в этой очень связанной Q / A , они действительно декодируют изображения перед запуском события load. У этой стратегии есть свои плюсы и минусы, а спецификации в настоящее время требуют только поведения без декодирования.

Теперь есть способы обойти эту проблему, как продемонстрировано в ранее связанной Q / A:

В поддерживаемых браузерах вы можете еще подождать HTMLImageElement.decode() обещание, которое заставит браузер полностью декодировать изображение, и, таким образом, будет готово нарисовать его сразу после выполнения обещания:

img.onload = (evt) => {
  img.decode().then(() => res(img));
};

// using big images so the latency is more visible
imgs = `https://upload.wikimedia.org/wikipedia/commons/d/dc/Spotted_hyena_%28Crocuta_crocuta%29.jpg
https://upload.wikimedia.org/wikipedia/commons/3/37/Mud_Cow_Racing_-_Pacu_Jawi_-_West_Sumatra%2C_Indonesia.jpg
https://upload.wikimedia.org/wikipedia/commons/c/cf/Black_hole_-_Messier_87.jpg`.split(/\s+/g);

const preloadImg = (...urls) => {
  const toolDiv = document.createElement('div');
  toolDiv.style = 'display: none';

  const load = url => {
    return new Promise(res => {
      const img = new Image();
      // we disable cache for demo
      img.src = url + '?r=' + Math.random();
      // further wait for the decoding
      img.onload = (evt) => {
        console.log('loaded data of a single image');
        img.decode().then(() => res(img));
      };
    });
  };
  const getImgs = imgs => {
    const promises = imgs.map(async url => {
      const img = await load(url);
      toolDiv.appendChild(img);
    });
    return Promise.all(promises);
  }
  getImgs(urls).then(() => {
    document.body.appendChild(toolDiv);
    toolDiv.style.display = "";
    console.log("all done");
  });
};
d.onclick = () => Array.from(x = document.querySelectorAll('div')).forEach(x => x.parentNode.removeChild(x));
c.onclick = () => {

  preloadImg(...imgs);

};
preloadImg(...imgs);
img {
  width: 100vw
}
<button id="c">click</button><button id="d">click</button>

И если вам требуется поддержка старых браузеров, вы можете довольно легко установить его, используя HTMLCanvasElement:

if( !HTMLImageElement.prototype.decode ) {
  const canvas = document.createElement( 'canvas' );
  canvas.width = canvas.height = 1; // low memory footprint
  const ctx = canvas.getContext('2d');
  HTMLImageElement.prototype.decode = function() {
    return new Promise( ( resolve, reject ) => {
      setTimeout( () => { // truly async
        try {
          ctx.drawImage(this,0,0);
          resolve()
        }
        catch( err ) {
          reject( err );
        }
      }, 0 );
    } );
  };
}
1 голос
/ 03 мая 2020

Примите ответ Кайидо, если он работает.

Используется предложение Кайидо. Все еще мерцает на Chrome. Держите это здесь, чтобы не загромождать основной пост ОП.

imgs=`https://i.imgur.com/OTQMjbE.jpg
https://i.imgur.com/gUpn5Jf.jpg
https://i.imgur.com/sIXWJWD.jpg
https://i.imgur.com/qhzfDD6.jpg`.split(/\s+/g);

const preloadImg = (...urls) => {
    const toolDiv = document.createElement('div');
    toolDiv.style = 'display: none';

    const load = url => {
        return new Promise(res => {
            const img = new Image();
            img.src = url;
            // using decode
            img.onload = (evt) => {
              img.decode().then(() => res(img));
            };
        });
    };
    const getImgs = imgs => {
        const promises = imgs.map(async url => {
            const img = await load(url);
            toolDiv.appendChild(img);
        });
        return Promise.all(promises);
    }
    getImgs(urls).then(() => {
        document.body.appendChild(toolDiv);
    });
};
preloadImg(...imgs);

let i = 1;
document.body.onwheel = () => {
    document.getElementById('aside').className = 'img' + (i++%4+1)
};
/*.css */
.img1::before,
.img1::after,.img1 {
    background-image: url(https://i.imgur.com/sIXWJWD.jpg); /* Would be ../img/dasdfsafadasdasda.jpg after file-loader */
}
.img2::before,
.img2::after,.img2 {
    background-image: url(https://i.imgur.com/qhzfDD6.jpg);
}
.img3::before,
.img3::after,.img3 {
    background-image: url(https://i.imgur.com/OTQMjbE.jpg);
}

.img4::before,
.img4::after,.img4 {
    background-image: url(https://i.imgur.com/gUpn5Jf.jpg);
}


body {
height:5000px
}
#aside {
  width: 500px;
  height: 500px;
}
<div><aside class="img1" id="aside"></aside></div>
...