You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

HTML中图片加载:区分无响应与错误响应

HTML中图片加载:区分无响应与错误响应

嘿,这个问题我之前做图片类项目时也踩过坑——确实img标签原生的error事件不会给你暴露HTTP响应状态码,因为它的设计就是只管加载成功/失败,不会把AJAX级别的响应细节给你。不过不用急,我们有几个实用的方案来解决这个问题,不用完全推翻你现在的逻辑:

方案1:用HEAD请求预检查(适合支持CORS的图片源)

这个方案是最准确的,因为我们可以先通过轻量的HEAD请求拿到图片的HTTP状态码,再决定是否加载图片或上报错误:

  • 如果返回200-399:正常加载图片
  • 如果返回4XX:直接上报并显示占位图
  • 如果请求失败(网络问题、跨域拦截):只显示占位图,不上报

代码示例:

function handleImageLoad(imgUrl) {
  const $mainImg = $('#main-img');
  
  // 先发送HEAD请求检查状态
  fetch(imgUrl, { method: 'HEAD' })
    .then(response => {
      if (response.ok) {
        // 状态正常,加载图片
        $mainImg.attr('src', imgUrl);
      } else if (response.status >= 400 && response.status < 500) {
        // 4XX错误,直接上报
        reportImage(`Image failed to load with ${response.status} error.`);
        $mainImg.attr('src', '/images/file-error.svg');
      } else {
        // 5XX或其他服务器错误,只显示占位图
        $mainImg.attr('src', '/images/file-error.svg');
      }
    })
    .catch(error => {
      // 请求失败(网络问题、跨域无CORS头),只显示占位图
      $mainImg.attr('src', '/images/file-error.svg');
    });
}

⚠️ 注意:如果图片服务器没有配置CORS允许HEAD请求,这个方法会触发跨域错误,此时可以看下面的方案2。

方案2:原生加载 + 重试过滤(适合跨域无法用AJAX的场景)

如果图片源不支持CORS,我们没法通过AJAX拿到状态码,但可以通过重试逻辑过滤临时网络问题:临时网络波动重试几次大概率能成功,而4XX错误重试多少次都没用。

代码示例:

let retryCount = 0;
const MAX_RETRIES = 2; // 可以根据实际情况调整

function loadImageWithRetry(imgUrl) {
  const $mainImg = $('#main-img');
  
  // 先解绑之前的error事件,避免重复绑定
  $mainImg.off('error').on('error', function() {
    retryCount++;
    if (retryCount >= MAX_RETRIES) {
      // 多次重试失败,判定为永久错误(大概率是4XX),上报
      reportImage("Image failed to load after multiple retries.");
      $mainImg.attr('src', '/images/file-error.svg');
      retryCount = 0; // 重置计数,避免影响下一张图
    } else {
      // 间隔一段时间后重试,间隔随重试次数递增
      setTimeout(() => {
        $mainImg.attr('src', imgUrl);
      }, 1500 * retryCount);
    }
  });
  
  // 首次加载图片
  $mainImg.attr('src', imgUrl);
}

这个方案虽然不能100%精准区分4XX和网络错误,但在实际生产环境中,临时网络问题重试2-3次基本能解决,上报的准确率非常高,而且不用处理CORS的麻烦。

方案3:Service Worker拦截请求(进阶完美方案)

如果你的网站是PWA或者已经用了Service Worker,可以通过它拦截所有图片请求,拿到完整的HTTP响应状态码,这是最完美的解决方案:

1. 在Service Worker中拦截图片请求

self.addEventListener('fetch', event => {
  // 只拦截图片请求
  if (event.request.destination === 'image') {
    event.respondWith(
      fetch(event.request)
        .then(response => {
          // 记录错误状态并通知页面
          if (!response.ok) {
            self.clients.matchAll().then(clients => {
              clients.forEach(client => {
                client.postMessage({
                  type: 'IMAGE_ERROR',
                  url: event.request.url,
                  status: response.status
                });
              });
            });
          }
          return response;
        })
        .catch(error => {
          // 网络错误,通知页面
          self.clients.matchAll().then(clients => {
            clients.forEach(client => {
              client.postMessage({
                type: 'IMAGE_NETWORK_ERROR',
                url: event.request.url
              });
            });
          });
          // 返回占位图或者直接报错
          return Response.error();
        })
    );
  }
});

2. 在页面中监听Service Worker的消息

// 确保Service Worker注册成功后监听消息
navigator.serviceWorker.addEventListener('message', event => {
  const $mainImg = $('#main-img');
  if (event.data.type === 'IMAGE_ERROR') {
    if (event.data.status >= 400 && event.data.status < 500) {
      reportImage(`Image ${event.data.url} failed with ${event.data.status} error.`);
    }
    $mainImg.attr('src', '/images/file-error.svg');
  } else if (event.data.type === 'IMAGE_NETWORK_ERROR') {
    // 网络错误,只显示占位图,不上报
    $mainImg.attr('src', '/images/file-error.svg');
  }
});

这个方案的优点是能100%区分所有错误类型,缺点是需要配置Service Worker,且要求网站在HTTPS环境下运行(localhost除外)。

最后补充

你之前代码里的event.responseCode不存在,是因为img的error事件是浏览器基于加载结果触发的,它不会携带HTTP响应的任何细节——只有AJAX请求或者Service Worker这种能拦截网络请求的机制,才能拿到完整的响应状态码。

根据你的项目场景选对应的方案就行,我自己做图片画廊项目时,跨域场景用方案2就足够应付大部分情况了,准确率很高,实现起来也最简单。

火山引擎 最新活动