【问题记录】接口异步处理图片预加载

21 阅读1分钟

背景:

上传图片的功能使用了第三方图片上传的sdk。当上传接口结束获取到数据时,绑定img的url是线上的图片地址,需要访问获取导致在界面效果上图片是空的情况。

方案:

给图片加loading效果直至图片预加载完成

image.png

图片预加载的方案

  • 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'));
})
)
);
}\