Skip to content

[JavaScript] 圖片錯誤處理在 JavaScript、React.js 和 Next.js 中的應用

· 8 min

圖片是網頁的重要元素之一,但有時圖片會因為各種因素而無法順利顯示,為了讓使用者體驗更好,圖片的錯誤處理方式是個實用且重要的小細節,前陣子第一次接觸到相關的技巧,為了加深印象,記錄也分享一下圖片的錯誤處理方式~

首先,先來看看如果沒有錯誤處理,會發生什麼事,假設我要載入一張圖片:

<img src="https://images.pexels.com/photos/1001682/pexels-photo.jpeg" alt="ocean-image" >

但因為圖片網址輸入錯誤,找不到該圖片,畫面會顯示破圖及 alt 文字:

找不到該圖,顯示破圖

為了使用者體驗與版面美觀,我們希望如果找不到該圖,可以顯示別的圖片告訴使用者此張圖不存在,也就是錯誤處理,也有人說是在錯誤時顯示 fallback image。

此篇會分為原生 JavaScript、React.js 與 Next.js 的 3 種處理方式,希望可以對錯誤處理更加熟悉。

原生 HTML + JavaScript#

原生 <img> 標籤就有 onerror 的事件處理機制,MDN 在 Image loading errors 的區塊提到:如果在載入或渲染圖片時發生錯誤,且已經為錯誤事件設定 onerror 事件,則將呼叫該事件,其中,所謂的「發生錯誤」包含:

因此,我們可以在<img> 新增 onerror 的事件處理,設定讓此圖片的 src 改為另一張 not found 或 404 圖片,告訴使用者此圖不存在,程式碼如下:

<img src="https://images.pexels.com/photos/1001682/pexels-photo.jpeg" alt="ocean-image"
onerror="this.onerror=null;this.src='https://fakeimg.pl/250x250/?text=Not%20Found';">

this.onerror=null 的用意是將此圖片的 onerror事件處理清空,這是為了防止無限循環。如果 not found 圖片也不存在,它就不會再次觸發 onerror 事件,可避免這樣的無限循環:嘗試載入 not found 圖 ➡️ 找不到 not found 圖 ➡️ 觸發 onerror 事件 ➡️ 嘗試載入 not found 圖 ➡️ 找不到 not found 圖 …。

簡言之,這行的目的是告訴此 img,「如果第一次圖片載入失敗,嘗試載入這個替代圖片。但如果替代圖片也載入失敗,就不要再嘗試了(不要再觸發 onerror 事件)」。

this.src='https://fakeimg.pl/250x250/?text=Not%20Found' 將此 <img>src 屬性設為 not found 圖片的路徑,不過其實不建議使用外部連結載入替代圖片,因為有可能會因為網路問題再次載入失敗,較好的方式是載入本地圖片檔案,會比外部連結更可靠,不過為了方便 demo,這次還是使用外部連結載入。

另一種載入替代圖片的可靠方式,是載入 Base64 編碼的圖片,直接嵌入 Base64 編碼的圖片,也不需依賴外部網絡請求,Ant Design 的 Image fallback 就是採用此方法,只要將上面 demo 的this.src改指向 Base64 編碼即可。

<Image
width={200}
height={200}
src="error"
fallback="data:image/png;base64,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"
/>

Ant Design 的 Image 錯誤處理採用 Base64 編碼

React.js#

React 處理方式其實和原生十分相似,利用 onError 處理錯誤狀況,並改載入另一張圖片;可以將錯誤處理的邏輯封裝起來,變成一個帶有錯誤處理邏輯的 ImageWithFallback 元件,程式碼如下(完整程式碼):

首先,用 useState 儲存是否為錯誤的狀態,當錯誤事件發生時(觸發 onError)執行 handleImageError,檢查現在是否已是錯誤狀態,如果不是錯誤狀態,就將狀態 set 為 true,因為 setState 會觸發重新渲染,重新渲染時 img 元件的 src 會因為錯誤狀態為 true 而載入 fallbackSrc,也就是 fallback image,fallbackSrc 這裡就可以放如果圖片載入失敗時希望顯示的圖片;另外,會判斷是否已是錯誤狀態是為了避免前面提過的無限循環問題,避免當 fallbackSrc 也載入失敗時會重複 setState 造成不必要的渲染。

function ImageWithFallback({
src,
fallbackSrc,
alt,
...props
}: ImageWithFallbackProps) {
const [imageError, setImageError] = useState(false);
const handleImageError = () => {
if (!imageError) {
setImageError(true);
}
};
return (
<img
src={imageError ? fallbackSrc : src}
alt={alt}
onError={handleImageError}
{...props}
/>
);
}

React 這部分是我想出來的寫法,應該也有其他方法可以達到一樣的目的,或是有更好的辦法也很歡迎大家回饋~

Next.js#

Next.js 的處理方式其實跟 React 大同小異,因為 Next.js 的 Image 元件沒有 fallback 或 fallbackSrc 的 prop,需要自己再封裝處理過,不過官方有提出一個 Using a fallback image with next/image 的 template,我就直接用了XD

const ImageWithFallback = ({
fallback = fallbackImage,
alt,
src,
...props
}) => {
const [error, setError] = useState(null)
useEffect(() => {
setError(null)
}, [src])
return (
<Image
alt={alt}
onError={setError}
src={error ? fallbackImage : src}
{...props}
/>
)
}

Next.js 官方提供的 ImageWithFallback template

Next.js 撰寫邏輯和我自己想的 React 解法有點不同,一樣用 useState 儲存錯誤狀態,錯誤事件發生時(觸發 onError)就執行 setError,原本這裡有點不解為什麼是直接給 setError 而沒有指定要 set 什麼值,但後來想到,在 JavaScript 或 React 中,當我將一個函式直接賦值給事件處理器(如 onError),則當事件發生時,該事件處理器會自動將事件物件(event object)作為參數傳遞給該函數,簡言之就是會執行setError(event),並因此觸發重新渲染,讓 src 改載入 fallbackImage

Next.js 另外用 useEffect 來重置錯誤狀態,每次 src 更動時都讓錯誤狀態回到初始值,可以更彈性的應對每個 src 的錯誤狀態,但上面我 React 的寫法就只能應對第一次的錯誤處理~不過相對的,Next.js 這寫法就可能會遇到 fallbackImage 載入失敗而造成的無限循環問題。

小結#

這篇文章記錄了圖片錯誤處理的方式,其實就是…發現原來原生 <img>onerror 屬性/事件可以用😂,事實上<img> 有蠻多可用的屬性,有機會要好好研究一下 MDN 文件了👀


Reference:#

如有任何問題歡迎聯絡、不吝指教✍️