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中预加载没有工作)。
异步预加载只在Chrome(和Opera)中有效,其他浏览器(FFF、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)处理,但我认为这不是问题所在。
需要帮助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';
};
但我还是认为这个类切换也不是问题。
你在这里面对的是 负荷 事件 只是告诉我们资源已经被获取,并且浏览器能够处理该媒体。还差一大步。图像解码.
事实上,即使所有的资源都已经从服务器上获取,而且浏览器可以从文件的头部解析出它将能够解码,以及其他数据,如媒体的尺寸,一些浏览器会等到真正需要的时候才会尝试真正解码图像数据("像素",如果你愿意的话)。这个过程还是需要时间的,在你真正将这些<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 );
} );
};
}
如果可以的话,接受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>