Vue3+UmiJS項目中實現(xiàn)WebP圖片自動轉(zhuǎn)換和優(yōu)化
前言
WebP 是一種現(xiàn)代圖片格式,相比傳統(tǒng)的 JPG/PNG 格式,通??梢詼p少 25-35% 的文件大小,某些圖片甚至可以減少 80% 以上。本文將介紹如何在 UmiJS + Vue 3 項目中實現(xiàn) WebP 圖片的自動轉(zhuǎn)換和智能加載。
功能特性
- 構(gòu)建時自動轉(zhuǎn)換:構(gòu)建時自動將 JPG/PNG 轉(zhuǎn)換為 WebP
- 智能格式選擇:自動檢測瀏覽器支持,優(yōu)先使用 WebP
- 自動回退:不支持的瀏覽器自動使用原始格式
- 性能優(yōu)化:使用緩存避免重復(fù)檢測和重復(fù)加載
- 零配置使用:組件化封裝,使用簡單
實現(xiàn)步驟
1. 安裝依賴
pnpm add -D imagemin imagemin-webp
2. 創(chuàng)建圖片轉(zhuǎn)換腳本
創(chuàng)建 scripts/convert-images.mjs:
import imagemin from "imagemin"
import imageminWebp from "imagemin-webp"
import path from "path"
import fs from "fs"
import { fileURLToPath } from "url"
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
/**
* 圖片轉(zhuǎn) WebP 腳本
* 將 src/assets 目錄下的 jpg/jpeg/png 圖片轉(zhuǎn)換為 webp 格式
*/
async function convertImages() {
const assetsDir = path.join(__dirname, "../src/assets")
// 檢查目錄是否存在
if (!fs.existsSync(assetsDir)) {
console.log("?? assets 目錄不存在,跳過圖片轉(zhuǎn)換")
return
}
console.log("??? 開始轉(zhuǎn)換圖片為 WebP 格式...")
try {
const files = await imagemin([`${assetsDir}/*.{jpg,jpeg,png}`], {
destination: assetsDir,
plugins: [
imageminWebp({
quality: 80, // 質(zhì)量 0-100,80 是質(zhì)量和文件大小的良好平衡
method: 6, // 壓縮方法 0-6,6 是最慢但壓縮率最高
}),
],
})
if (files.length === 0) {
console.log("?? 沒有找到需要轉(zhuǎn)換的圖片")
} else {
console.log(`? 成功轉(zhuǎn)換 ${files.length} 張圖片為 WebP 格式:`)
files.forEach((file) => {
const fileName = path.basename(file.destinationPath)
const originalSize = fs.statSync(
file.sourcePath.replace(/\.webp$/, path.extname(file.sourcePath))
).size
const webpSize = fs.statSync(file.destinationPath).size
const reduction = ((1 - webpSize / originalSize) * 100).toFixed(1)
console.log(` - ${fileName} (減少 ${reduction}%)`)
})
}
} catch (error) {
console.error("? 圖片轉(zhuǎn)換失敗:", error.message)
process.exit(1)
}
}
// 執(zhí)行轉(zhuǎn)換
convertImages()
3. 創(chuàng)建圖片工具函數(shù)
創(chuàng)建 src/utils/image.ts:
/**
* 圖片工具函數(shù) - 支持 WebP 格式
*/
const WEBP_SUPPORT_CACHE_KEY = "__webp_support__"
/**
* 檢測瀏覽器是否支持 WebP 格式(帶緩存)
* 使用 localStorage 緩存檢測結(jié)果,避免重復(fù)檢測
*/
export function checkWebPSupport(): Promise<boolean> {
// 先檢查緩存
if (typeof window !== "undefined" && window.localStorage) {
const cached = window.localStorage.getItem(WEBP_SUPPORT_CACHE_KEY)
if (cached !== null) {
return Promise.resolve(cached === "true")
}
}
// 如果沒有緩存,進行檢測
return new Promise((resolve) => {
const webP = new Image()
webP.onload = webP.onerror = () => {
const supported = webP.height === 2
// 緩存結(jié)果
if (typeof window !== "undefined" && window.localStorage) {
window.localStorage.setItem(WEBP_SUPPORT_CACHE_KEY, String(supported))
}
resolve(supported)
}
webP.src =
"data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA"
})
}
/**
* 同步獲取 WebP 支持狀態(tài)(從緩存)
* 如果緩存不存在,默認返回 true(現(xiàn)代瀏覽器都支持)
* 這樣可以避免初始加載非 WebP 資源
*/
export function getWebPSupportSync(): boolean {
if (typeof window === "undefined" || !window.localStorage) {
// SSR 環(huán)境,默認返回 true
return true
}
const cached = window.localStorage.getItem(WEBP_SUPPORT_CACHE_KEY)
if (cached !== null) {
return cached === "true"
}
// 沒有緩存時,默認假設(shè)支持(現(xiàn)代瀏覽器都支持 WebP)
// 如果實際不支持,后續(xù)檢測會更新緩存,下次就會使用正確的值
return true
}
/**
* 將圖片 URL 轉(zhuǎn)換為 WebP 格式
* 支持多種轉(zhuǎn)換方式:
* 1. 內(nèi)置圖片:直接替換擴展名
* 2. 在線圖片:使用圖片代理服務(wù)或 CDN 轉(zhuǎn)換
*
* @param url 原始圖片 URL
* @param options 轉(zhuǎn)換選項
* @returns WebP 格式的 URL
*/
export function convertToWebP(
url: string,
options: {
// 是否強制使用 WebP(即使瀏覽器不支持)
force?: boolean
// 圖片代理服務(wù) URL(用于在線圖片轉(zhuǎn)換)
proxyUrl?: string
// CDN 轉(zhuǎn)換參數(shù)(如騰訊云、阿里云等)
cdnParams?: string
} = {}
): string {
const { force = false, proxyUrl, cdnParams } = options
// 如果是 data URL,直接返回
if (url.startsWith("data:")) {
return url
}
// 如果是內(nèi)置圖片(相對路徑或 umi 處理后的路徑),替換擴展名
// 支持 umi 處理后的路徑格式:/static/yay.7d162f31.jpg -> /static/yay.7d162f31.webp
if (
url.startsWith("./") ||
url.startsWith("../") ||
(!url.startsWith("http") && !url.startsWith("data:"))
) {
// 匹配 .jpg, .jpeg, .png 擴展名(可能包含 hash)
return url.replace(/\.(jpg|jpeg|png)(\?.*)?$/i, ".webp$2")
}
// 在線圖片處理
if (url.startsWith("http://") || url.startsWith("https://")) {
// 方式1: 使用圖片代理服務(wù)
if (proxyUrl) {
return `${proxyUrl}?url=${encodeURIComponent(url)}&format=webp`
}
// 方式2: 使用 CDN 參數(shù)轉(zhuǎn)換(如騰訊云、阿里云等)
if (cdnParams) {
const separator = url.includes("?") ? "&" : "?"
return `${url}${separator}${cdnParams}`
}
// 方式3: 使用在線圖片轉(zhuǎn)換服務(wù)(如 Cloudinary、ImageKit 等)
// 這里提供一個示例,實際使用時需要根據(jù)服務(wù)商調(diào)整
// return `https://your-image-service.com/convert?url=${encodeURIComponent(url)}&format=webp`;
// 方式4: 簡單替換擴展名(如果服務(wù)器支持)
return url.replace(/\.(jpg|jpeg|png)(\?.*)?$/i, ".webp$2")
}
return url
}
/**
* 獲取圖片的最佳格式 URL
* 如果瀏覽器支持 WebP,返回 WebP 格式;否則返回原始格式
*
* @param originalUrl 原始圖片 URL
* @param webpUrl WebP 格式的 URL(可選,如果不提供則自動生成)
* @param webpSupported 瀏覽器是否支持 WebP(可選,如果不提供則自動檢測)
* @returns 最佳格式的 URL
*/
export async function getBestImageUrl(
originalUrl: string,
webpUrl?: string,
webpSupported?: boolean
): Promise<string> {
const isSupported =
webpSupported !== undefined ? webpSupported : await checkWebPSupport()
if (isSupported) {
return webpUrl || convertToWebP(originalUrl)
}
return originalUrl
}
/**
* 預(yù)加載圖片
*/
export function preloadImage(url: string): Promise<void> {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = () => resolve()
img.onerror = reject
img.src = url
})
}
4. 創(chuàng)建 WebP 圖片組件
創(chuàng)建 src/components/WebPImage.vue:
<template>
<picture>
<!-- 如果支持 WebP,優(yōu)先使用 WebP -->
<source
v-if="webpSupported && webpSrc"
:srcset="webpSrc"
type="image/webp"
/>
<!-- 回退到原始格式 -->
<img
:src="fallbackSrc"
:alt="alt"
:width="width"
:height="height"
:class="imgClass"
:style="imgStyle"
:loading="loading"
@load="handleLoad"
@error="handleError"
/>
</picture>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue"
import {
checkWebPSupport,
getWebPSupportSync,
convertToWebP,
} from "../utils/image"
interface Props {
// 原始圖片 URL(必需)
src: string
// WebP 格式的 URL(可選,如果不提供則自動生成)
webpSrc?: string
// 圖片描述
alt?: string
// 圖片寬度
width?: string | number
// 圖片高度
height?: string | number
// CSS 類名
imgClass?: string
// 內(nèi)聯(lián)樣式
imgStyle?: string | Record<string, any>
// 懶加載
loading?: "lazy" | "eager"
// 圖片代理服務(wù) URL(用于在線圖片轉(zhuǎn)換)
proxyUrl?: string
// CDN 轉(zhuǎn)換參數(shù)
cdnParams?: string
}
const props = withDefaults(defineProps<Props>(), {
alt: "",
loading: "lazy",
})
const emit = defineEmits<{
load: [event: Event]
error: [event: Event]
}>()
// 使用同步方法獲取初始值,避免初始加載非 WebP 資源
// 如果緩存不存在,默認假設(shè)支持(現(xiàn)代瀏覽器都支持)
// 后續(xù)異步檢測會更新這個值
const webpSupported = ref(getWebPSupportSync())
// 計算 WebP 格式的 URL
const webpSrc = computed(() => {
// 如果明確提供了 webpSrc,直接使用
if (props.webpSrc) {
return props.webpSrc
}
// 對于在線圖片,只有在提供了轉(zhuǎn)換方式時才生成 WebP URL
const isOnlineImage =
props.src.startsWith("http://") || props.src.startsWith("https://")
if (isOnlineImage) {
if (!props.proxyUrl && !props.cdnParams) {
// 如果沒有提供轉(zhuǎn)換方式,返回空字符串,使用原始格式
return ""
}
// 有轉(zhuǎn)換方式,生成 WebP URL
return convertToWebP(props.src, {
proxyUrl: props.proxyUrl,
cdnParams: props.cdnParams,
})
}
// 對于內(nèi)置圖片(通過 import 導(dǎo)入的),自動嘗試使用 WebP 版本
// 構(gòu)建腳本會在同目錄下生成 .webp 文件,通過替換擴展名來引用
// 如果 webp 文件不存在,瀏覽器會自動回退到原始圖片
return convertToWebP(props.src, {})
})
// 回退到原始格式
const fallbackSrc = computed(() => props.src)
// 在后臺異步檢測 WebP 支持(如果緩存不存在)
// 這樣可以更新緩存,但不影響初始渲染
onMounted(async () => {
// 如果已經(jīng)有緩存,就不需要再次檢測
if (typeof window !== "undefined" && window.localStorage) {
const cached = window.localStorage.getItem("__webp_support__")
if (cached === null) {
// 沒有緩存,進行檢測并更新
const supported = await checkWebPSupport()
// 如果檢測結(jié)果與初始假設(shè)不同,更新狀態(tài)
// 但此時圖片可能已經(jīng)加載,瀏覽器會使用 <picture> 標簽自動選擇
if (supported !== webpSupported.value) {
webpSupported.value = supported
}
}
} else {
// 沒有 localStorage,直接檢測
webpSupported.value = await checkWebPSupport()
}
})
const handleLoad = (event: Event) => {
emit("load", event)
}
const handleError = (event: Event) => {
emit("error", event)
}
</script>
5. 配置 TypeScript 類型聲明
創(chuàng)建 src/types/images.d.ts:
/**
* 圖片文件類型聲明
*/
declare module "*.jpg" {
const content: string
export default content
}
declare module "*.jpeg" {
const content: string
export default content
}
declare module "*.png" {
const content: string
export default content
}
declare module "*.gif" {
const content: string
export default content
}
declare module "*.webp" {
const content: string
export default content
}
declare module "*.svg" {
const content: string
export default content
}
declare module "*.ico" {
const content: string
export default content
}
declare module "*.bmp" {
const content: string
export default content
}
6. 配置 UmiJS
更新 .umirc.ts,添加 WebP 文件支持:
import { defineConfig } from "umi"
import { routes } from "./src/router/index"
const isPrd = process.env.NODE_ENV === "production"
export default defineConfig({
npmClient: "pnpm",
presets: [require.resolve("@umijs/preset-vue")],
manifest: {
fileName: "manifest.json",
},
// 開發(fā)環(huán)境 vite ,線上環(huán)境 webpack 打包
vite: isPrd ? false : {},
chainWebpack: function (config, { webpack }) {
// 配置 webp 文件支持(與 jpg/png 一樣處理)
// umi 默認已經(jīng)處理圖片,但可能不包含 webp,這里確保 webp 被正確處理
// 使用 asset/resource 類型,讓 webpack 將 webp 文件作為靜態(tài)資源處理
if (!config.module.rules.has("webp")) {
config.module
.rule("webp")
.test(/\.webp$/)
.type("asset/resource")
.generator({
filename: "static/[name].[hash:8][ext]",
})
}
config.optimization.runtimeChunk(true)
return config
},
codeSplitting: {
jsStrategy: "depPerChunk",
},
hash: true,
history: { type: "hash" },
routes,
})
7. 更新 package.json
在 package.json 中添加構(gòu)建腳本:
{
"scripts": {
"dev": "umi dev",
"build": "pnpm build:images && umi build",
"build:prd": "pnpm build:images && UMI_ENV=prd umi build",
"build:images": "node scripts/convert-images.mjs",
"postinstall": "umi setup",
"start": "npm run dev",
"preview": "umi preview",
"analyze": "ANALYZE=1 umi build"
}
}
使用方法
基本使用
<script setup lang="ts">
import WebPImage from "../components/WebPImage.vue"
import yayImage from "../assets/yay.jpg"
import yayWebpImage from "../assets/yay.webp"
</script>
<template>
<div>
<!-- 方式1: 自動檢測并使用 WebP -->
<WebPImage :src="yayImage" width="388" alt="圖片" />
<!-- 方式2: 手動指定 WebP 文件 -->
<WebPImage
:src="yayImage"
:webp-src="yayWebpImage"
width="388"
alt="圖片"
/>
</div>
</template>
在線圖片(使用 CDN 轉(zhuǎn)換)
<template>
<!-- 阿里云 OSS -->
<WebPImage
src="https://your-bucket.oss-cn-hangzhou.aliyuncs.com/image.jpg"
cdn-params="x-oss-process=image/format,webp"
width="500"
alt="CDN 圖片"
/>
<!-- 騰訊云 COS -->
<WebPImage
src="https://your-bucket.cos.ap-shanghai.myqcloud.com/image.jpg"
cdn-params="imageMogr2/format/webp"
width="500"
alt="CDN 圖片"
/>
</template>
在線圖片(使用代理服務(wù))
<script setup lang="ts">
const proxyUrl = "https://your-image-proxy.com/convert"
</script>
<template>
<WebPImage
src="https://example.com/image.jpg"
:proxy-url="proxyUrl"
width="500"
alt="在線圖片"
/>
</template>
工作原理
1. 構(gòu)建時轉(zhuǎn)換
- 運行
pnpm build時,會先執(zhí)行build:images腳本 - 腳本掃描
src/assets目錄下的所有 JPG/PNG 圖片 - 使用
imagemin-webp轉(zhuǎn)換為 WebP 格式 - 轉(zhuǎn)換后的文件保存在同一目錄
2. 運行時加載
- 組件初始化時,使用
getWebPSupportSync()同步獲取 WebP 支持狀態(tài) - 如果緩存不存在,默認假設(shè)支持(避免重復(fù)加載)
- 使用
<picture>標簽,瀏覽器自動選擇最佳格式 - 后臺異步檢測,更新緩存供下次使用
3. 性能優(yōu)化
- 緩存機制:使用
localStorage緩存檢測結(jié)果 - 避免重復(fù)加載:初始值使用同步方法獲取,避免先加載原始圖片再加載 WebP
- 智能回退:使用
<picture>標簽,瀏覽器自動處理回退
效果對比
測試結(jié)果顯示,yay.jpg (177KB) 轉(zhuǎn)換為 yay.webp (23KB) 后,文件大小減少了 87.0%!
注意事項
- 開發(fā)環(huán)境:開發(fā)時不會自動轉(zhuǎn)換,需要手動運行
pnpm build:images - Git 管理:建議將
.webp文件添加到.gitignore,因為它們可以通過構(gòu)建腳本自動生成 - 質(zhì)量調(diào)整:可在
scripts/convert-images.mjs中調(diào)整質(zhì)量參數(shù)(默認 80) - 瀏覽器兼容性:現(xiàn)代瀏覽器都支持 WebP,組件會自動檢測并回退
總結(jié)
通過以上配置,我們實現(xiàn)了:
- 構(gòu)建時自動轉(zhuǎn)換圖片為 WebP
- 智能格式選擇和自動回退
- 性能優(yōu)化,避免重復(fù)加載
- 組件化封裝,使用簡單
這套方案可以顯著提升頁面加載速度,特別是在圖片較多的場景下效果明顯。
到此這篇關(guān)于Vue3+UmiJS項目中實現(xiàn)WebP圖片自動轉(zhuǎn)換和優(yōu)化的文章就介紹到這了,更多相關(guān)Vue3圖片自動轉(zhuǎn)換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Vue2.x-directive的學(xué)習(xí)筆記
這篇文章主要介紹了詳解Vue2.x-directive的學(xué)習(xí)筆記,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-07-07
vue-cli+webpack項目打包到服務(wù)器后,ttf字體找不到的解決操作
這篇文章主要介紹了vue-cli+webpack項目打包到服務(wù)器后,ttf字體找不到的解決操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
Vue3動態(tài)路由(響應(yīng)式帶參數(shù)的路由)變更頁面不刷新的問題解決辦法
問題來源是因為我的開源項目Maple-Boot項目的網(wǎng)站前端,因為項目主打的內(nèi)容發(fā)布展示,所以其中的內(nèi)容列表頁會根據(jù)不同的菜單進行渲染不同的路由,本文降介紹Vue3動態(tài)路由變更頁面不刷新的問題解決辦法,需要的朋友可以參考下2024-07-07
vxe-table?使用?vxe-upload?在表格中實現(xiàn)非常強大的粘貼上傳圖片和附件功能
本文通過實例代碼介紹了vxe-table渲染器的強大功能,配合 vxe-upload 上傳,比如復(fù)制或者截圖一張圖片,通過粘貼方式快速粘貼到單元格中,能支持單張、多張、查看、預(yù)覽功能,感興趣的朋友跟隨小編一起看看吧2024-12-12
關(guān)于vue.js中this.$emit的理解使用
本文主要介紹了關(guān)于vue.js中this.$emit的理解使用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
在Vue項目中用fullcalendar制作日程表的示例代碼
這篇文章主要介紹了在Vue項目中用fullcalendar制作日程表,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08

