背景:
上传图片的功能使用了第三方图片上传的sdk。当上传接口结束获取到数据时,绑定img的url是线上的图片地址,需要访问获取导致在界面效果上图片是空的情况。
方案:
给图片加loading效果直至图片预加载完成
图片预加载的方案
- 1、方案一:使用new image()、img标签或者创建link标签preload实现,简单方便,不需要太多控制
- 2、方案二:写一个类批量预加载做并发控制
- 3、方案三:结合 Intersection Observer 的图片预加载+懒加载
方案一如上
方案二:
class ImagePreloader {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.queue = [];
this.running = 0;
}
preload(url) {
return new Promise((resolve, reject) => {
this.queue.push({ url, resolve, reject });
this.process();
});
}
async process() {
if (this.running >= this.maxConcurrent || this.queue.length === 0) {
return;
}
this.running++;
const { url, resolve, reject } = this.queue.shift();
try {
const img = await this.loadImage(url);
resolve(img);
} catch (error) {
reject(error);
} finally {
this.running--;
this.process(); // 处理下一个
}
}
loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Failed to load: ${url}`));
img.src = url;
});
}
// 批量预加载
async preloadAll(urls) {
return Promise.all(urls.map(url => this.preload(url)));
}
}
// 使用
const preloader = new ImagePreloader(3); // 最多3个并发
await preloader.preloadAll(['url1', 'url2', 'url3', ...]);
方案三:
const { root = null, rootMargin = '200px', threshold = 0.1 } = options;
const observer = new IntersectionObserver((entries) => {
entries.forEach(async (entry) => {
if (entry.isIntersecting) {
const img = entry.target;
const dataSrc = img.getAttribute('data-src');
if (!dataSrc) return;
try {
await preloadImage(dataSrc);
img.src = dataSrc; // 替换占位图
observer.unobserve(img); // 解除监听
} catch (error) {
console.warn('预加载失败:', error.message);
}
}
});
}, { root, rootMargin, threshold });
imageElements.forEach(img => observer.observe(img));
}
// 使用时,HTML写成:
/\* <img data-src="https://example.com/image.jpg" src="转存失败,建议直接上传图片文件 placeholder.jpg" alt="...转存失败,建议直接上传图片文件">
\*/\`
\`
function preloadWithObserver(urls) {
const images = urls.map(url => {
const img = new Image();
img.src = url;
return img;
});
return Promise.all(
images.map(img =>
new Promise((resolve, reject) => {
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('Failed'));
})
)
);
}\