網(wǎng)頁(yè)在獲取圖片資源的時(shí)候妈倔,經(jīng)常會(huì)由于 資源路徑無(wú)效、網(wǎng)絡(luò)環(huán)境不理想绸贡、服務(wù)器過(guò)載 等原因?qū)е沦Y源加載超時(shí)或失敗盯蝴。為了保證良好的用戶(hù)體驗(yàn)毅哗,我們需要對(duì)圖片加載做容錯(cuò)處理。
最簡(jiǎn)單的方式就是通過(guò)綁定 img
元素的 error
事件结洼,在圖片加載失敗時(shí)顯示備用圖片 error.jpg
黎做。
若采用此方式叉跛,需要在
error
事件觸發(fā)時(shí)取消事件的綁定松忍,避免當(dāng)error.jpg
也加載失敗時(shí)死循環(huán)拉取資源。
<img src="image-path.jpg" onerror="this.onerror=null;this.src='error.jpg'">
如果需要對(duì) 背景圖片(background-image
) 實(shí)施容錯(cuò)處理呢筷厘?
如果需要在圖片加載過(guò)程中加 loading 效果呢鸣峭?
這個(gè)時(shí)候就需要 “替身” 發(fā)揮作用了 ——
我們可以利用一個(gè)對(duì)用戶(hù)不可見(jiàn)的 img
元素,來(lái)驗(yàn)證圖片資源的合法性酥艳。
jQuery 環(huán)境
額外創(chuàng)建一個(gè)不插入 DOM 節(jié)點(diǎn)樹(shù)的 img
元素 “替身”摊溶,src
屬性值為目標(biāo)資源的 url。若 “替身” 能成功獲取目標(biāo)資源(load
事件觸發(fā))充石,即讓目標(biāo)節(jié)點(diǎn)加載該資源莫换,否則(error
事件觸發(fā))加載備用資源。
注意骤铃,重復(fù)加載同一個(gè)資源時(shí)并不會(huì)產(chǎn)生額外的網(wǎng)絡(luò)消耗拉岁,瀏覽器會(huì)從本地緩存獲取該資源。
在 jQuery 環(huán)境下惰爬,建議以插件的形式擴(kuò)展喊暖,維持鏈?zhǔn)秸{(diào)用的特性:
$.fn.img = function(opts) {
const $img = document.createElement('img');
// 未加載完成時(shí),顯示 loading
this.css('background-image', `url('${opts.loading}')`);
$img.onload = () => {
this.css('background-image', `url('${opts.src}')`);
};
$img.onerror = () => {
this.css('background-image', `url('${opts.error}')`);
};
$img.src = opts.src;
return this;
}
$('.image').img({
src: 'success.jpg',
error: 'error.jpg',
loading: 'loading.gif',
});
預(yù)覽地址:https://codepen.io/JunreyCen/pen/WBapBw
jQuery + 模板引擎
如果使用了模板引擎(譬如 lodash
的 _.template
函數(shù))的渲染方式撕瞧,且希望維持 數(shù)據(jù)驅(qū)動(dòng)視圖 的模式時(shí)陵叽,可以參考下面的處理:
<div id="app"></div>
<template id="tpl">
<div class="image" style="background-image: url('<%= loading %>')"></div>
<img
style="display: none"
src="<%= src %>"
onload="$(this).siblings('.image').css('background-image', 'url(<%= src %>)')"
onerror="$(this).siblings('.image').css('background-image', 'url(<%= error %>)')">
</template>
<script>
$(function() {
$('#app').append(_.template($('#tpl').html())({
src: 'success.jpg',
error: 'error.jpg',
loading: 'loading.gif',
}));
});
</script>
題外話(huà),推薦把 demo 中的處理封裝成模板組件丛版,配合 CSS 命名空間 就可以實(shí)現(xiàn)組件化了巩掺。鑒于 jQuery + 模板引擎 這類(lèi)技術(shù)棧流行度已經(jīng)沒(méi)那么高了,這里不再單獨(dú)提供例程页畦。
Vue 環(huán)境
我們通過(guò)綁定 vue 指令 來(lái)實(shí)現(xiàn)圖片的容錯(cuò)處理胖替。其好處在于,每當(dāng)圖片資源的 src
被初始化或更新時(shí)寇漫,vue 指令都可以捕捉到變化刊殉,并容錯(cuò)處理后再響應(yīng)式地作用于目標(biāo)節(jié)點(diǎn)。
vue 官方文檔 中對(duì)于 自定義指令 有詳細(xì)的教程州胳,這里就不多說(shuō)了记焊,直接貼代碼。
<div id="app"></div>
<template id="tpl">
<div class="image" v-img="{
src: 'success.jpg',
error: 'error.jpg',
loading: 'loading.gif',
}"></div>
</template>
<script>
function imgHandler($el, binding) {
const opts = binding.value;
const $img = document.createElement('img');
$el.style.backgroundImage = `url('${opts.loading}')`;
$img.onload = () => {
$el.style.backgroundImage = `url('${opts.src}')`;
};
$img.onerror = () => {
$el.style.backgroundImage = `url('${opts.error}')`;
};
$img.src = opts.src;
}
Vue.directive('img', {
inserted: imgHandler,
update: imgHandler,
});
new Vue({
el: '#app',
template: '#tpl',
});
</script>
踩坑點(diǎn)
基于上面提供的 vue demo栓撞,我們來(lái)模擬一個(gè)場(chǎng)景:
- 目標(biāo)節(jié)點(diǎn)的
src
被更新遍膜;
譬如碗硬,src
先被賦值為path-to-image-A.jpg
,再更新為path-to-image-B.jpg
瓢颅。 - 圖片資源的加載速度不理想恩尾;
此時(shí),萬(wàn)一資源 A
的加載速度比資源 B
還要慢挽懦,就會(huì)出現(xiàn)歷史資源(A
)把最新資源(B
)覆蓋掉的問(wèn)題翰意。我們稍微修改下 demo 來(lái)實(shí)現(xiàn)這個(gè)場(chǎng)景:
<div class="image" v-img="{
// ...
src,
delay,
}"></div>
<script>
function imgHandler($el, binding) {
// ...
if (opts.delay) {
// 模擬圖片加載延遲
setTimeout(() => {
$img.src = opts.src;
}, opts.delay);
} else {
$img.src = opts.src;
}
}
new Vue({
data() {
return {
src: '',
delay: 0,
};
},
mounted() {
this.delay = 200;
this.src = 'success.jpg';
this.$nextTick(() => {
this.delay = 0;
this.src = 'success_2.jpg';
});
},
});
</script>
或者直接看 demo 效果:https://codepen.io/JunreyCen/pen/JqmNQP
優(yōu)化方案
解決方案也很簡(jiǎn)單,我們只需要把所有 “替身” 都記錄下來(lái)信柿,在更新時(shí)把上一次創(chuàng)建的 “替身” 清理掉冀偶。即使出現(xiàn)歷史資源的捕獲時(shí)機(jī)比最新資源的還要靠后的情況,由于歷史資源的 onload
和 onerror
方法已經(jīng)被重置渔嚷,不會(huì)產(chǎn)生影響进鸠。
最終方案例程:https://github.com/JunreyCen/blog-demo/blob/master/image-handler/vue.2.html