图片错误自动重载

2021-07-20 16:52:45 浏览数 (1)

平常在我们的项目中,必然会引用很多图片。

但是我们通常只管给图片赋值一个链接

爱怎么加载怎么加载,失败我也不管

这其实对于一个应用来说是非常不完善的

因为每个用户的网络情况无法预估(比如地铁上),图片加载失败必然导致页面就无法浏览或者体验差

这肯定不是一个好应用

比如我在地铁上打开施肥,网络不行,图片全部加载失败了,直接返回又重新进来,很烦躁的啊

所以通常我们会给图片加上一个 失败重载的机制。

那么今天我们就来看一下怎么实现这个 图片失败重载的机制。

1

简单描述

先简单说一下基本的处理

每一个 img 在加载失败后重新加载最多3次,超过3次就 换上默认图片

因为我们在引入我们js 或者 直出的时候,页面已经有 img 元素在加载了所以我们需要对已经存在的 img 进行处理

所以处理分两条路

1、处理已经存在的 img

2、监听动态插入的 img

来看一下流程图

那么下来我们来分别看下这两个处理流程

2

处理现存图片

我们需要获取到所有现有的图片,然后逐个遍历去判断图片是否加载失败

但是对于可能已经加载完成的图片我们怎么判断是否加载失败呢?

img 元素如果加载完成,会有一个属性 complete = true

但是我们还不知道这个图片是成功还是失败

我们需要从另一个属性去判断

那就是 naturalWidth,这个表示图片的原始宽度,如果该值为0,那么就表示这个图片加载失败啦

条件就是

3

动态监听新 img错误

我们是不是 监听 img 元素的插入,然后给 img 元素加上一个 onerror 事件?

当然不是啦

我们可以在全局监听一个 error 事件,并且在 事件回调中 判断元素是 img 才进行处理

那么具体是怎么做呢

代码语言:javascript复制
document.body.addEventListener(

第三个参数useCapture必须设置为 true,表示事件采取事件捕获原则。默认是true,采取事件冒泡原则

因为img或者script标签发生error时不会向上冒泡,所以父级以上元素监听error则不会被触发。不过既然不会冒泡,我们只能使用捕获保证先执行父级元素事件

4

处理图片错误

好了,上面说完了两条处理分支,现在来说一下共同的错误处理分支

我们的原则是

1、不处理懒加载图片

2、图片加载未超过3次,重载图片,超过3次使用默认图片

1不处理懒加载图片

首先懒加载的图片在没有划上屏幕的时候,是没有加载的,src为空,只在data-src或者lazy-src保存原图片链接

所以这些图片不适用于错误重载,直接跳过

2图片加载未超3次则重载

我们要怎么知道图片重载了几次?

做法是,只要重载之后,在图片链接后面拼上一个参数 reloadcounts = 1继续重载,参数值就获取 1

如果次数超过3,那么就使用默认图片

具体代码如下

代码语言:javascript复制
// 给图片 url 加上 重载次数参数

5

代码实现

接下来看下简单的代码实现

代码语言:javascript复制
// 处理已经存在的图片
function observeStatic(target) {
  getImgElements(target, (nodes) => {    nodes.forEach((node) => {      
        const lazySrc =
          node.getAttribute('data-src') || node.getAttribute('lazy-src');      
        if (!lazySrc) {
          wrapImg(node);
        }
    });
  });}


// 监听动态插入的 img
function observeDynamic(target) {
  target.addEventListener(    
    'error',
    errorHandler,    
    true // 只有捕获阶段能捕捉到img加载error
  );  


  const errorHandler = (e) => {    
      const element = e.target;
   
      if (element instanceof HTMLImageElement) {      
          const src = element.getAttribute('src');    
       // lazyload图片不处理    
        const lazySrc =   
          element.getAttribute('data-src') || 
          element.getAttribute('lazy-src');      
          if (!src || lazySrc) {        
            return;
          }


          setTimeout(() => {        
            // 延迟到onError执行后处理
            ImgErrorHandler(element);
          }, 0);
    }
  };}


// 图片错误处理函数
const ImgErrorHandler = (element) => {  
  const src = element.src;  
  const isOrigin = !element[originalSrcKey];  


  // 图片还没有 重载过 || 没有超过重载次数
  if (isOrigin || !ifImgLoadOverMaxCount(src)) {    
    // 如果重来没有加载,那么就保存元数据
    if (isOrigin) {
      element[originalSrcKey] = src;
    }    element.src = addReloadFlag(src);    
    return;
  }  


  // defaultUrl也加载失败,没救了
  element.onerror = null;
  element.src = DEFAULT_IMG;};


// 获取到元素下所有的 img 标签
function getImgElements(target, callback) {  
   const nodes = target.querySelectorAll('img');  
   if (typeof callback === 'function') {
     callback([].slice.call(nodes));
   }}


// 处理 img 标签
function wrapImg(element) {  


  if (wrappedImgElements.indexOf(element) !== -1) {    
     return;
  }


  wrappedImgElements.push(element);  


  const originalOnError = element.onerror;
  element.onerror = function (e) {    
    if (typeof originalOnError === 'function') {
      originalOnError.call(this, e);
    }

    ImgErrorHandler(element);  };  


  if (element.complete && element.naturalWidth === 0) {    
    // 意味着没加载成功,直接重试
    ImgErrorHandler(element);
  }}


let DYNAMIC_TARGET = null;


function obseverImg(target) {  
  if (DYNAMIC_TARGET) {    
    // 不允许多次observe
    return;
  }
  DYNAMIC_TARGET = target;
  observeStatic(target);
  observeDynamic(target);
}

然后使用的时候,如果你是监听全局,可以像这样

代码语言:javascript复制
obseverImg(docuemnt.body)

但是有时不会是这样,因为不会全局使用一张默认图片的

所以这里支持传入 目标dom 元素,只处理部分 img

4

总结

通常在我们的应用中,一个完善的流程不止是

失败-》重载*3-》失败-》默认

在中间可能还要加上一个代理的流程,比如可能图片访问不了,那么我们加个代理可能就好了

或者中间还有更多的判断,比如符合某个规则的图片才有重载机制 等等

反正我们的目的就是增加图片成功的概率,但是本文只是记录一下基本原理,所以复杂更完善的流程就省略了

最后

鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵, 如果有任何描述不当的地方,欢迎后台联系本人

0 人点赞