如何优雅处理图片异常

4,589 阅读3分钟

前言

不同浏览器对加载失败图片的图标展示不统一,所以给定一个默认的失败图片就尤为重要。

正好前几天处理了一下公司首页图片 error 默认图片的问题,就趁着记忆没有消失分享一下这篇文章。

error

img 标签有一个 error 事件,通过它我们可以捕捉到异常,使用起来也很简单

<img src="abc.xxx" alt="xxx" class="cs-img" />
<img src="abc.xxx" alt="xxx" class="cs-img" onerror="this.src = 'xxxx.png'" />;
// 或者
var img = document.querySelector(".cs-img");
img.addEventListener("error", function(e) {
  e.target.src = "xxxx.png";
});

全局

上面的方法没有问题,不过需要我们手动管理图片这样写的话维护成本可能很高,有可能你只是想为图片失败统一处理。

我们希望可以统一监听到error的事件来完成,不过图片的error处于事件模型的第二阶段也就是目标阶段,是不会向上冒泡的,但是也是可以通过window.addEventListener.error来完成监听。

顺便说下window.addEventListener.error不仅可以监听到图片的失败也可以监听到 css,js 之类加载错误,注意区分window.onerrorwindow.addEventListener.error之间的区别,前者是 js 运行错误,后者是资源错误,对于事件模型网上的例子很多,这里不做展开来说了。

来看下如何用

window.addEventListener(
  "error",
  function(event) {
    var dom = event.target;
    if (/img/i.test(dom.nodeName)) {
      dom.src = "xxxx.png";
    }
  },
  true
);

注意 addEventListener 第二个参数必须是 true,默认为 false,表示只在冒泡阶段出发,但是上面我们说过图片的 error 事件并不会向上冒泡所以是不会捕捉到的。

重试次数

上面的代码没有考虑到备选src也会失效的问题,如果备选src失效就会导致图片无限重试,下面就抛砖引玉写一种方法。

window.addEventListener(
  "error",
  function(event) {
    var dom = event.target;
    if (!/img/i.test(dom.nodeName)) {
      return;
    }
    // 不存在返回null
    var retry = +dom.getAttribute("data-retry");
    if (retry >= 3) {
      // 绝对安全的图片
      dom.src =
        "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
    } else {
      dom.src = "xxxx.png";
      dom.setAttribute("data-retry", (retry += 1));
    }
  },
  true
);

动态填充

最后分享一个动态模板渲染出来的网页(例如,网页的内容是从后台的编辑器 html 直接插入的)如何监听error,如果不考虑兼容问题可以在head使用window.addEventListener.error方法,但是如果需要兼容性很高不妨试试下面这种。

const img = Array.prototype.slice.call(document.images);
for (let i = 0; i < img.length; i++) {
  var dom = img[i];
  const image = document.createElement("img");
  image.src = dom.src;
  image.style.display = "none";
  document.body.appendChild(image);
  image.onerror = function(event) {
    dom.src = "xxxx.png";
    document.body.removeChild(image);
  };
  image.onload = function() {
    document.body.removeChild(image);
  };
}

上面我们说了假设内容是直接通过 html 代码插入的,我们可能监听不到图片的默认错误事件,那么我们可以在网页加载完成之后重试一遍所有的 img,在为 img 指定一次错误事件就 OK 了。

最后

为了方便演示,上面的代码我都没有做兼容性的补充,但是在实际生产中上面代码需要polyfill尤其是 dom 的一些语法,推荐简单一些的做法可以把上面的例子转化为 jquery 的语法。