前端如何實現模糊圖像加載特效
2024-09-191.54Front EndNextjsReact
前言
圖片在前端是一個經常出現的元素,網頁的圖片可以吸引用戶的訪問者的注意力,讓內容整體更加生動,但當一張圖片檔案大小太大時,用戶可能就需要較長的時間才能看到圖片加載完成,無疑會造成幾個問題 :
- 影響用戶體驗
加載時間過長會讓用戶感到焦慮,進而影響整體的網站體驗,可能導致用戶離開網站。
- 降低信任感
網站加載速度過慢可能會讓用戶對網站的專業性和可靠性產生懷疑,降低品牌信任
- 導致重複請求
如果圖片加載過慢,用戶可能會多次刷新頁面,這樣會增加伺服器的請求負擔,影響整體網站性能
改善方法
我們可以壓縮圖片轉 webp、懶加載、建置 CDN 等手段優化圖片加載速度,而我正要介紹的是優化用戶體驗的方法 —— 「Blurred Image Placeholder」也就是模糊圖像佔位符,如下我們看一下效果 :


程式設計
要做出這般的效果,我第一直覺想到的是使用兩張圖片一張圖片搭配 css 的 filter blur 設為模糊,另一張圖片搭配屬性 onLoad 加載完成後,替換掉模糊圖片,可能的程式碼如下:
import React, { useState } from 'react'; const App: React.FC = () => { const [isLoaded, setIsLoaded] = useState<boolean>;(false); return ( <div style={{ position: 'relative' }}>; {!isLoaded && ( <img src={'/Doge.jpg'} alt={'Doge'} style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', filter: 'blur(20px)', }} />; )} <img src={'/Doge.jpg'} alt={'Doge'} style={{ display: isLoaded ? 'block' : 'none', width: '100%', height: 'auto', }} onLoad={() =>; setIsLoaded(true)} />; </div>; ); }; export default App;
看似完美,但實則不起作用,因為模糊圖片與替換圖片都是指向同一張圖片,而模糊圖片仍然要等到圖片加載完成後,樣式才正式套用,因此模糊樣式就毫無意義。

我們需要使用其他做法才能克服,試想一下,模糊圖片只是為了展示一個佔位符而存在,且不需要清晰可見,所以是不是可以賦予它低像素、低檔案大小的圖像,進而提升載入速度,使之優先展示呢?
這個答案是肯定的,但是問題又來了,這個低畫質像素圖片該由誰來產呢?假設在前端產,那還需要經過讀取檔案、轉換檔案系列動作,這些過程中也許圖片早就加載完成了呢?
其實我們大可讓後端傳遞圖片url到前端的API,多帶上一個屬性 blurredImageUrl,資料結構可能如下 :
{ "imgUrl": "https//example.com/Doge.jpg", "blurredImageUrl": "https//example.com/Doge-blurred.jpg" }
看似不錯,但不建議這麼做,因為會導致整體 API Response 架構變得非常詭異,多傳這個屬性除了令人匪夷所思外,在多圖片的架構下,每張圖片都以物件形式存在,想想都讓人頭疼。
那麼模糊圖片佔位符是不是遙不可及,無法處理了呢?其實仍有解方的,可以依賴耳熟能響的NextJS幫助我們實現。
Next.js 實現 Blurred Image Placeholder
眾所皆知的 Next.js,以 SSG、SSR 等服務器渲染聞名,它賦予了前端操作 Server 端的能力,大大拓展前端可控上限,假如把這個模糊圖片的產生過程放到 Next.js 的 Server 端,爾後,吐到客戶端的圖片只要專注呼叫與渲染,必可減少瀏覽器解析 JS 的負擔。
Image
Image 這個組件由 next/image
引入,封裝了處理伺服器端處理模糊圖像佔位符的功能,只需加上屬性placeholder='blur'
即可,如下 :
import Image from 'next/image' import Doge from '@/public/Doge.jpg' export default function Home() { return ( <main> <Image src={Doge} alt='Doge' width={500} height={500} placeholder='blur' /> </main> ) }
打開開發者工具,發現呼叫了兩張圖片,如圖,類型為 svg+xml 的檔案被壓縮到只有十分之一 :

看起來還不錯,可惜它限定靜態圖片引用,不支援動態圖片,但實際情況來說,動態圖片肯定是大宗,我將介紹其使用方式。
靜態圖片是指採用 import 圖片的方式添加圖片,動態圖片指src直接引用圖片路徑,如果 placeholder屬性搭配動態圖片 Next.js 會報錯
在 Server 端生產低畫質圖片
對於非靜態圖片,Next.js 提供一個屬性 blurDataURL,我們唯一需要做的事情,就是把產出來的模糊圖像放到這個位置即可,我們可以搭配套件 placeholder 幫助我們更好的完成
安裝套件
npm i sharp @plaiceholder/next
官方文件寫得非常清楚了,我們只需依樣畫葫蘆即可,這邊我選擇封裝成函數方便覆用,我們優化官方釋例,提供一個更通用的函數,支援本地及遠端圖片
/* /utils/index.ts */ import fs from 'fs/promises' import path from 'path' import { getPlaiceholder } from 'plaiceholder' const isRemoteImage = (image: string) => { const regex = /^(http|https):\/\/.*/ return regex.test(image) } export async function generateBlurredBase64(image: string) { try { let buffer // 判斷是否是遠端路徑 if (isRemoteImage(image)) { // 如果是遠端路徑,從 URL 獲取圖片 const res = await fetch(image) if (!res.ok) { throw new Error('Failed to fetch remote image') } buffer = Buffer.from(await res.arrayBuffer()) } else { // 如果是本地路徑,從 public 目錄讀取圖片 const imagePath = path.join(process.cwd(), 'public', image) buffer = await fs.readFile(imagePath) } const { base64 } = await getPlaiceholder(buffer) return base64 } catch (err) { console.error(err) throw new Error('Error generating blurred image') } }
使用方式
import Image from 'next/image' import { generateBlurredBase64 } from '../utils' export default async function Home() { // 注意!生產過程是異步 const base64 = await generateBlurredBase64('/Doge.jpg') return ( <main> <Image src={'/Doge.jpg'} alt='Doge' width={500} height={500} placeholder='blur' blurDataURL={base64} // 可以直接放上base64這是沒問題的 /> </main> ) }
我們甚至可以更極致優化,直接再封裝一層 Image
import Image, { ImageProps } from 'next/image' import { generateBlurredBase64 } from '../utils' const ImageWithPlaceholder = async ({ src, alt, ...restProps }: ImageProps) => { const base64 = await generateBlurredBase64(src as string) return ( <Image src={src} alt={alt} placeholder='blur' blurDataURL={base64} {...restProps} /> ) }
使用時
import { ImageWithPlaceholder } from './ImageWithPlaceholder' export default function Home() { return ( <main> <ImageWithPlaceholder src={'/Doge.jpg'} alt='Doge' width={500} height={500} /> </main> ) }
此時的 Image 扮演的是一個 server component,並且享有模糊圖像佔位符的能力了!
查看開發者工具效果運行良好,Good!
這邊介紹以 Next.js 的 app router 為主,如果要在 page router 中實踐,必須寫在 getserversideprops或 getstaticprops 中,因為那裡是 Next.js 中唯一可以實現 Server 端程式碼的地方,app router 優勢也在此嶄露無遺(操作 Server 端程式的細粒度從頁面縮小到組件為單位)
結語
以上分享是自我實踐中的思緒整理,會發現其實看似簡單的功能,底層實踐沒想像中容易,所幸,有Next.js 讓前端有操作服務器的能力,我們可以站在巨人的肩膀做事,使用開箱即用的已封裝組件與套件,大大節省開發時間與困難度,希望這篇文章有幫助到正愁著怎麼製作 Blurred Image Placeholder 的人!
參考
https://nextjs.org/docs/pages/api-reference/components/image
https://nextjs.org/docs/messages/placeholder-blur-data-url
https://medium.com/@Furki4_4/make-your-image-loading-blurry-in-next-js-0f0e5bf3dc7c