前端图片加载异常兜底方案

186 阅读2分钟

1.直接在img标签上使用内联事件处理图片加载失败的情况

<img src='xxxxx' onerror="this.src = 'default.png'">

但是这种方式代码侵入性太大,指不定就会有地方漏掉。

全局img添加事件

const imgs = document.getElementsByTagName('img') 
Array.prototype.forEach.call(imgs, img =&gt; { 
    img.addEventListener('error', e =&gt; { 
    e.target.src = 'default.png' 
    }) 
 })

2.利用error事件捕获

document.addEventListener( 'error', e => { 
    let target = e.target 
    const tagName = target.tagName || '' 
    if (tagName.toLowerCase = 'img') { 
        target.src = 'default.png' 
    } 
    target = null 
}, true )

上面的方案有两个缺点:

  • 如果是因为网络差导致加载失败,那么加载默认图片的时候也极大概率会失败,于是会陷入无限循环。
  • 如果是网络波动导致的加载失败,那么图片可能重试就会加载成功。

所以我们可以为每个img标签额外添加一个data-retry-times计数属性,当重试超过限制次数后就用base64图片作为默认兜底。

3.添加data-retry-times计数属性

document.addEventListener( 'error', e => { 
    let target = e.target 
    const tagName = target.tagName || '' 
    const curTimes = Number(target.dataset.retryTimes) || 0 
    if (tagName.toLowerCase() === 'img') { 
        if (curTimes >= 3) { 
            target.src = 'data:image/png;base64,xxxxxx' 
        } else { 
            target.dataset.retryTimes = curTimes + 1 
            target.src = target.src 
        } 
    } 
    target = null 
}, true )

4.CSS处理的最优解

上面方式是采用替换src的方式来展示兜底图,这种解决方式有一个缺陷:

  • 原图的资源链接无法从标签上获取(虽然可以通过加data-xxx属性的方式hack解决)。

所以还有一种更好的方式,就是利用CSS伪元素::before和::after覆盖原本元素,直接展示兜底base64图片。

css样式如下

img.error { 
    display: inline-block; 
    transform: scale(1); 
    content: ''; 
    color: transparent; 
} 
img.error::before { 
    content: ''; 
    position: absolute; 
    left: 0; 
    top: 0; 
    width: 100%; 
    height: 100%; 
    background: #f5f5f5 url(data:image/png;base64,xxxxxx) no-repeat center / 50% 50%; 
} 
img.error::after { 
    content: attr(alt); 
    position: absolute; 
    left: 0; 
    bottom: 0; 
    width: 100%; 
    line-height: 2; 
    background-color: rgba(0,0,0,.5); 
    color: white; 
    font-size: 12px; 
    text-align: center; 
    white-space: nowrap; 
    overflow: hidden; 
    text-overflow: ellipsis; 
}

js代码如下

document.addEventListener( 'error', e => { 
    let target = e.target 
    const tagName = target.tagName || '' 
    const curTimes = Number(target.dataset.retryTimes) || 0 
    if (tagName.toLowerCase() === 'img') { 
        if (curTimes >= 3) { 
            target.classList.remove('error') 
            target.classList.add('error') 
        } else { 
            target.dataset.retryTimes = curTimes + 1 
            target.src = target.src 
        } 
    } 
    target = null 
}, true )