平常在我们的项目中,必然会引用很多图片。
但是我们通常只管给图片赋值一个链接
爱怎么加载怎么加载,失败我也不管
这其实对于一个应用来说是非常不完善的
因为每个用户的网络情况无法预估(比如地铁上),图片加载失败必然导致页面就无法浏览或者体验差
这肯定不是一个好应用
比如我在地铁上打开施肥,网络不行,图片全部加载失败了,直接返回又重新进来,很烦躁的啊
所以通常我们会给图片加上一个 失败重载的机制。
那么今天我们就来看一下怎么实现这个 图片失败重载的机制。
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-》失败-》默认
在中间可能还要加上一个代理的流程,比如可能图片访问不了,那么我们加个代理可能就好了
或者中间还有更多的判断,比如符合某个规则的图片才有重载机制 等等
反正我们的目的就是增加图片成功的概率,但是本文只是记录一下基本原理,所以复杂更完善的流程就省略了
最后
鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵, 如果有任何描述不当的地方,欢迎后台联系本人