亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

js前端圖片加載異常兜底方案

 更新時(shí)間:2022年06月16日 14:47:21   作者:隨風(fēng)丶逆風(fēng)  
這篇文章主要為大家介紹了js前端圖片加載異常兜底方案,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

網(wǎng)絡(luò)環(huán)境總是多樣且復(fù)雜的,一張圖片可能會(huì)因?yàn)榫W(wǎng)路狀況差而加載失敗或加載超長(zhǎng)時(shí)間,也可能因?yàn)闄?quán)限不足或者資源不存在而加載失敗,這些都會(huì)導(dǎo)致用戶體驗(yàn)變差,所以我們需要一個(gè)圖片加載異常的兜底方案。

<img>加載錯(cuò)誤解決方案

內(nèi)聯(lián)事件

直接在img標(biāo)簽上使用內(nèi)聯(lián)事件處理圖片加載失敗的情況,但是這種方式代碼侵入性太大,指不定就會(huì)有地方漏掉。

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

全局img添加事件

第一個(gè)方案侵入性太大,我們將入口收斂,為所有img標(biāo)簽統(tǒng)一添加error處理事件。

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

利用error事件捕獲

為每個(gè)img添加事件處理函數(shù)的代價(jià)還是高了點(diǎn),我們知道一般事件會(huì)經(jīng)歷三個(gè)階段:

  • 捕獲階段
  • 處于目標(biāo)階段
  • 冒泡階段

根據(jù)MDN文檔中的描述:

When a resource (such as an <img> or <script>) fails to load, an error event using interface Event is fired at the element that initiated the load, and the onerror() handler on the element is invoked. These error events do not bubble up to window, but can be handled with a EventTarget.addEventListener configured with useCapture set to true.

我們可以知道img和srcipt標(biāo)簽的error并不會(huì)冒泡,但是會(huì)經(jīng)歷捕獲階段和處于目標(biāo)階段。前兩個(gè)方案就是利用處于目標(biāo)階段觸發(fā)事件函數(shù),這一次我們?cè)诓东@階段截獲并觸發(fā)函數(shù),從而減少性能損耗。

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

替換src方式的最優(yōu)解

上面的方案有兩個(gè)缺點(diǎn):

  • 如果是因?yàn)榫W(wǎng)絡(luò)差導(dǎo)致加載失敗,那么加載默認(rèn)圖片的時(shí)候也極大概率會(huì)失敗,于是會(huì)陷入無(wú)限循環(huán)。
  • 如果是網(wǎng)絡(luò)波動(dòng)導(dǎo)致的加載失敗,那么圖片可能重試就會(huì)加載成功。

所以我們可以為每個(gè)img標(biāo)簽額外添加一個(gè)data-retry-times計(jì)數(shù)屬性,當(dāng)重試超過(guò)限制次數(shù)后就用base64圖片作為默認(rèn)兜底。

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
)

CSS處理的最優(yōu)解

上面方式是采用替換src的方式來(lái)展示兜底圖,這種解決方式有一個(gè)缺陷:

  • 原圖的資源鏈接無(wú)法從標(biāo)簽上獲?。m然可以通過(guò)加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
)

<img>加載超時(shí)解決方案

目前大多數(shù)應(yīng)用都會(huì)接入CDN來(lái)加速資源請(qǐng)求,但是CDN存在節(jié)點(diǎn)覆蓋不全的問(wèn)題,導(dǎo)致DNS查詢超時(shí),此時(shí)如果切換Domain可能就會(huì)加載成功。

嗅探切換Domain(CNAME)

我們可以使用嗅探的方式,測(cè)試CDN提供的Domain是否能夠正常訪問(wèn),如果不行或者超時(shí)就及時(shí)切換成可訪問(wèn)Domain。 其中有幾個(gè)注意點(diǎn):

  • 為了防止嗅探圖片緩存,需要添加時(shí)間戳保持新鮮度
  • Image圖片加載沒(méi)有超時(shí)機(jī)制,使用setTimeout模擬超時(shí)
// 防止嗅探圖片存在緩存,添加時(shí)間戳保持新鮮度
export const imgUri = `/img/xxxxx?timestamp=${Date.now()}${Math.random()}`;

export const originDomain = 'https://sf6-xxxx.xxxx.com'

// 可采用配置下發(fā)的方式
export const cdnDomains = [
  'https://sf1-xxxx.xxxx.com',
  'https://sf3-xxxx.xxxx.com',
  'https://sf9-xxxx.xxxx.com',
];

export const validateImageUrl = (url: string) => {
  return new Promise<string>((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      resolve(url);
    };
    img.onerror = (e: string | Event) => {
      reject(e);
    };
    // promise的狀態(tài)不可變性,使用setTimeout模擬超時(shí)
    const timer = setTimeout(() => {
      clearTimeout(timer);
      reject(new Error('Image Load Timeout'));
    }, 10000);
    img.src = url;
  });
};

export const setCDNDomain = () => {
  const cdnLoop = () => {
    return Promise.race(
      cdnDomains.map((domain: string) => validateImageUrl(domain + imgUri)),
    ).then(url => {
      window.shouldReplaceDomain = true;
      const urlHost = url.split('/')[2];
      window.replaceDomain = urlHost;
    });
  };

  return validateImageUrl(`${originDomain}${imgUri}`)
    .then(() => {
      window.shouldReplaceDomain = false;
      window.replaceDomain = '';
    })
    .catch(() => {
      return cdnLoop();
    });
};

// 替換URL
export const replaceImgDomain = (src: string) => {
  if (src && window.shouldReplaceDomain && window.replaceDomain) {
    return src.replace(originDomain.split('/')[2], window.replaceDomain);
  }
  return src;
};

服務(wù)端下發(fā)Domain(CNAME)

該方案需要后臺(tái)同學(xué)配合,由后臺(tái)判斷當(dāng)前當(dāng)前可用Domain并返回。

getUsefulDomain()
    .then(e => {
        window.imgDomain = e.data.imgDomain || ''
    })

background-image加載異常解決方案

實(shí)際應(yīng)用中背景圖也會(huì)加載失敗,通常這些元素沒(méi)有error事件,所以也就無(wú)從捕獲error事件了。此時(shí)就可以利用dispatchEvent,它同樣擁有捕獲階段,MDN文檔上是這么介紹的:

Dispatches an Event at the specified EventTarget, (synchronously) invoking the affected EventListeners in the appropriate order. The normal event processing rules (including the capturing and optional bubbling phase) also apply to events dispatched manually with dispatchEvent().

可以看到支持度也還是可以的,我們首先需要自定義一個(gè)事件并初始化這個(gè)事件,在背景圖加載失敗的時(shí)候觸發(fā)這個(gè)自定義事件,最后在上層捕獲這個(gè)事件并執(zhí)行事件函數(shù)。

自定義事件

自定義事件有兩種方式:

  • 使用createEvent() 和 initEvent(),但是根據(jù)MDN文檔,initEvent方法已經(jīng)從瀏覽器標(biāo)準(zhǔn)中移除,并不安全,但支持度很高。

  • 使用new Event()的方式,但是支持率稍微差一點(diǎn)

這里以第二種為例,根據(jù)MDN文檔的用法創(chuàng)建一個(gè)自定義事件:

const event = new Event('bgImgError')

嗅探加載情況

使用前面定義的方法嗅探圖片資源的情況。

validateImageUrl('xxx.png')
    .catch(e => {
        let ele = document.getElementById('bg-img')
        if (ele) {
            ele.dispatchEvent('bgImgError')
        }
        ele = null
    })

添加事件捕獲

document.addEventListener(
    'bgImgError',
    e => {
        e.target.style.backgroundImage = "url(data:image/png;base64,xxxxxx)"
    },
    true
)

CDN與DNS知識(shí)匯總

圖片加載失敗后CSS樣式處理最佳實(shí)踐

優(yōu)雅的處理圖片異常

以上就是js前端圖片加載異常兜底方案的詳細(xì)內(nèi)容,更多關(guān)于js前端圖片加載異常方案的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論