Promise.all的异步图片预加载在非Chrome浏览器上无法使用。

问题描述 投票:1回答:1
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的截图,在Edge 18中预加载没有工作)。

screenshot from Edge, where preloading is not working

异步预加载只在Chrome(和Opera)中有效,其他浏览器(FFF、Edge、IE)会出现闪烁,就像根本没有预加载一样(但显示时没有额外的http请求,这意味着这些图片已经被获取和缓存了)。

我检查了浏览器对 Promise, Promise.allasync await 并没有问题。

然而传统的同步预加载在各个浏览器上都能完美地工作。

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

这些图片由Webpack文件加载器和Babel(core-js & runtime)处理,但我认为这不是问题所在。

需要帮助thx!


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


我在这里再说明一下。这些图片显示为背景图片通过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
javascript http dom webpack preload
1个回答
2
投票

你在这里面对的是 负荷 事件 只是告诉我们资源已经被获取,并且浏览器能够处理该媒体。还差一大步。图像解码.

事实上,即使所有的资源都已经从服务器上获取,而且浏览器可以从文件的头部解析出它将能够解码,以及其他数据,如媒体的尺寸,一些浏览器会等到真正需要的时候才会尝试真正解码图像数据("像素",如果你愿意的话)。这个过程还是需要时间的,在你真正将这些<img>元素全部附加到文档之前,这些浏览器才会开始进行该操作,因此才会出现闪烁。

现在,为什么Chrome浏览器不会面临这个问题呢?因为正如在 这个非常相关的QA 他们实际上是在发射图像之前对图像进行解码的。load 事件。这种策略有利有弊,规范目前只要求无解码行为。

现在,有一些方法可以解决这个问题,在之前链接的QA中就有演示。

在支持的浏览器中,你可以进一步等待... ... 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
投票

如果可以的话,接受Kaiido的回答。

使用了Kaiido的建议。 在 Chrome 浏览器上还是会闪烁。 为了不影响OP的主帖,把这个放在这里。

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>
© www.soinside.com 2019 - 2024. All rights reserved.