Promise.all 的异步图像预加载不适用于非 Chrome 浏览器

Async image preloading by Promise.all not working on non-Chrome browsers

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);
    });
};

您确实在 Elements 中看到了包含所有这些图像的 <div>,并且路径是正确的:

<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 的屏幕截图,其中预加载不起作用):


异步预加载仅在 Chrome(和 Opera)中有效,任何其他浏览器(ff、edge、ie)都会导致闪烁,就像根本没有预加载一样(但显示时没有额外的 http 请求,这意味着那些图像被提取和缓存)。

我检查了 PromisePromise.allasync await 的浏览器支持,没有问题。

然而,传统的同步预加载在浏览器中完美运行:

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

这些图像是由带有 Babel(core-js 和运行时)的 Webpack 文件加载器处理的,但我认为这不是问题所在。

需要帮助谢谢!!


更新:我只是借了室友的iPhone8,没有闪烁。



我会在这里说得更清楚。这些图像显示为背景图像,由 css classes 切换并由鼠标滚轮触发,例如:

/*.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';
};

通过 class 重命名切换图像。

但我仍然认为这个 class 开关也不是问题。



最后更新:@Kaiido 的方法有效。此问题是由获取的图像的部分可用性引起的。完全可用性需要以下解码过程。 whatwg

你在这里面对的是 load event 只告诉我们资源已经被获取并且浏览器能够处理媒体。
还有一大步:图像解码.

事实上,即使所有资源都已从服务器获取并且浏览器可以从文件的 headers 中解析,它也能够解码它,以及其他数据,如媒体的维度,一些浏览器会等到真正需要它时再尝试实际解码图像数据(如果你愿意的话,"pixels")。
这个过程仍然需要时间,并且在您将所有这些 元素实际附加到文档之前,这些浏览器将开始执行该操作,因此会出现闪烁。

现在,为什么 Chrome 没有遇到这个问题?因为正如 this very related Q/A 中所展示的那样,它们在触发 load 事件之前确实对图像进行了解码。这种策略有利有弊,规范目前只要求 no-decode 行为。

现在,有解决该问题的方法,如先前链接的 Q/A:

所示

在支持浏览器的情况下,可以进一步等待HTMLImageElement.decode()promise,这会强制浏览器对图片进行完全解码,从而在promise resolve后直接绘制:

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 );
    } );
  };
}

凯多的回答如果有效请采纳。

采纳了凯多的建议。 Chrome 上仍然闪烁。把它放在这里,这样它就不会弄乱 OP 的主要 post.

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>