SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader
〇、前言:
本篇将介绍:
- vue2 使用
require()
引入 svg 使用 -
vue3
+ts
+vite
使用vite-svg-loader
插件引入svg 使用 - 并最终实现代码提示一样使用图标文件
一、问题描述
我有一个长期维护的开源项目:《标题日记》。
近期想从原来的 vue2
改成 现在的 vue3 + ts + vite
的方式,就遇到了这个 SVG 引入的问题,最终还是解决了,接下来把结果整理一下:
vue2
-
vue3
+ts
+vite
在这两个项目中,我都使用了将 SVG
内容赋值给 img
标签的 src
的展现方式:
<img src="img可识别的svg内容,我也不知道是什么内容,可能应该是 url 格式的数据" />
- 好处之一是可以随意调整它的大小,并且跟 img 一样完全自适应。
- 另一个好处就是将所有图标统一管理,用的时候直接用
svgIcons.logo.logo_avatar
这种方式赋值,比较爽。
但在由 vue2 转到 vue3+ts+vite 的方式之后,以前的 require()
方式不能用了,所以需要另找一种方法实现它。
网上普遍的用法是将 svg
封装成一个组件,通过传值 string
的方式展示图标,我不喜欢这种。
感觉那种适合类似 elementUI
那种成系统的、各组件都完美整合它的这种框架,而我自己这个是完全自己从 0 开始写的,所以多半还是比较喜欢以 img
的方式展示图标。
二、vue2 使用 require
引入 svg 文件
我项目中的 svg 图标目录结构是这样的:
/src/assets/img/**/*.svg
将所有 svg 文件都放到一个文件 SvgIcons.js
中,这里用到了 require
,使用的时候直接将对应的值赋值给 <img src="url" />
即可。
线上 github 中的实例: https://github.com/KyleBing/diary/blob/master/src/assets/img/SvgIcons.js
内容如下:
export default {
// LOGO
logo: require('./logo/logo.svg'),
logo_content: require('./logo/logo_content.svg'),
logo_content_saved: require('./logo/logo_content_saved.svg'),
logo_title: require('./logo/logo_title.svg'),
logo_title_saved: require('./logo/logo_title_saved.svg'),
logoIcon: {
login: require('./logo/logo_login.svg'),
register: require('./logo/logo_register.svg'),
changePassword: require('./logo/logo_change_password.svg'),
changeAvatar: require('./logo/logo_avatar.svg'),
},
}
这样做的好处是,可以静态的使用:
<div class="logo-wrapper">
<div :class="['logo', {valid: avatarLink} ]">
<img :src="avatarLink || SvgIcons.logoIcon.login" alt="Diary Logo">
</div>
</div>
也可以动态的使用:
<template>
<div class="weather-selector">
<div class="weather" @click="chooseWeather(item.title)" v-for="item in weathers" :key="item.title">
<img
:src="weatherSelected === item.title? SvgIcons.weather[item.title + '_active'] : SvgIcons.weather[item.title]"
:alt="item.name"
:title="item.name">
</div>
</div>
</template>
三、vue3 + ts + vite 直接使用 svg
vite 可以直接在 img
的 src
中引用 svg 文件作为源,但这种方法只适合死的图片,一点都不能 “动”,不能变化。不能动态的渲染,只是作为图片使用。
<div class="logo-wrapper">
<div :class="['logo', {valid: avatarLink} ]">
<img v-if="avatarLink" :src="avatarLink" alt="Diary Logo">
<img v-else src="../../assets/icons/logo/logo_login.svg" alt="">
</div>
</div>
四、vue3 + ts + vite: 使用 vite-svg-loader 载入 svg ,动态使用
当换成 vue3
+ vite
之后,上面第二步的 require()
方法就不能用了,因为 vite
没有 require
,其实我也不知道 require
是哪里来的,反正就是在 vite
中不能用就是了。
那就需要另找方法了,试过使用 component
的方式等等,不是我喜欢的方式,另外在控制它大小的时候不会控制了。
最终还是找到了这个 vite-svg-loader
以 url
数据的方式引入 svg 文件内容。
相关阅读:
- 如何缩放 SVG 【英】
- 官方说明:vite-svg-loader官方:以 url 的方式引入 svg 【英】
说下过程:
1. 添加 vite-svg-loader
yarn 或 npm
yarn add vite-svg-loader
# 或者
npm i vite-svg-loader
2. 在 vite.config.ts
中添加 svgLoader()
- 打开
vite.config.ts
文件 - 引入 svgLoader
import svgLoader from "vite-svg-loader"
- 在
plugins
字段中添加它,如下,跟vue()
平级
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import svgLoader from "vite-svg-loader"
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
svgLoader()
]
})
3. 创建 SVG_ICONS.ts 文件,包含所有用到的 SVG 文件
在 SVG_ICONS.ts
文件中以这样的方式引入 svg
文件
注意:
后面添加 ?url
才表示以 url 的方式引入,后面才能直接赋值给 img.src
属性,少了?url
是不行的。
这样做相当于上面第二步中的 require()
import logo from "./logo/logo.svg?url"
import logo_content from "./logo/logo_content.svg?url"
import logo_content_saved from "./logo/logo_content_saved.svg?url"
我特别喜欢批量修改这种配置文件:
完整代码是这样的:
// tab icons
import tab_invitation from "./tab/invitation.svg?url"
import tab_add from "./tab/add.svg?url"
import tab_back from "./tab/back.svg?url"
import tab_close from "./tab/close.svg?url"
import tab_delete from "./tab/delete.svg?url"
import tab_done from "./tab/done.svg?url"
import tab_done_saved from "./tab/done_saved.svg?url"
import tab_done_changed from "./tab/done_changed.svg?url"
import tab_edit from "./tab/edit.svg?url"
import tab_menu from "./tab/menu.svg?url"
import tab_recover from "./tab/recover.svg?url"
import tab_search from "./tab/search.svg?url"
import tab_bill from "./tab/bill.svg?url"
import tab_bill_simple from "./tab/bill_simple.svg?url"
import tab_card from "./tab/card.svg?url"
import tab_share from "./tab/share.svg?url"
import tab_eye_close from "./tab/eye_close.svg?url"
import tab_eye_open from "./tab/eye_open.svg?url"
import tab_list_simple from "./tab/list_simple.svg?url"
import tab_list_detail from "./tab/list_detail.svg?url"
import tab_statistics from "./tab/statistics.svg?url"
import tab_key from "./tab/key.svg?url"
import tab_folder from "./tab/folder.svg?url"
import tab_todo from "./tab/todo.svg?url"
import tab_todo_active from "./tab/todo_active.svg?url"
import tab_others from "./tab/others.svg?url"
import tab_category from "./tab/category.svg?url"
import tab_year from "./tab/year.svg?url"
import tab_about from "./tab/about.svg?url"
import tab_invitation_black from "./tab/invitation_black.svg?url"
import tab_add_black from "./tab/add_black.svg?url"
import tab_back_black from "./tab/back_black.svg?url"
import tab_close_black from "./tab/close_black.svg?url"
import tab_delete_black from "./tab/delete_black.svg?url"
import tab_done_black from "./tab/done_black.svg?url"
import tab_edit_black from "./tab/edit_black.svg?url"
import tab_menu_black from "./tab/menu_black.svg?url"
import tab_recover_black from "./tab/recover_black.svg?url"
import tab_search_black from "./tab/search_black.svg?url"
import tab_bill_black from "./tab/bill_black.svg?url"
import tab_bill_simple_black from "./tab/bill_simple_black.svg?url"
import tab_card_black from "./tab/card_black.svg?url"
import tab_share_black from "./tab/share_black.svg?url"
import tab_eye_close_black from "./tab/eye_close_black.svg?url"
import tab_eye_open_black from "./tab/eye_open_black.svg?url"
import tab_list_simple_black from "./tab/list_simple_black.svg?url"
import tab_list_detail_black from "./tab/list_detail_black.svg?url"
import tab_statistics_black from "./tab/statistics_black.svg?url"
import tab_key_black from "./tab/key_black.svg?url"
import tab_folder_black from "./tab/folder_black.svg?url"
import tab_todo_black from "./tab/todo_black.svg?url"
import tab_todo_active_black from "./tab/todo_active_black.svg?url"
import tab_others_black from "./tab/others_black.svg?url"
import tab_category_black from "./tab/category_black.svg?url"
import tab_year_black from "./tab/year_black.svg?url"
import tab_about_black from "./tab/about_black.svg?url"
// LOGO
import logo from "./logo/logo.svg?url"
import logo_content from "./logo/logo_content.svg?url"
import logo_content_saved from "./logo/logo_content_saved.svg?url"
import logo_title from "./logo/logo_title.svg?url"
import logo_title_saved from "./logo/logo_title_saved.svg?url"
import logo_login from "./logo/logo_login.svg?url"
import logo_register from "./logo/logo_register.svg?url"
import logo_change_password from "./logo/logo_change_password.svg?url"
import logo_avatar from "./logo/logo_avatar.svg?url"
// Weather
import weather_cloudy from "./weather/cloudy.svg?url"
import weather_cloudy_active from "./weather/cloudy_active.svg?url"
import weather_cloudy_white from "./weather/cloudy_white.svg?url"
import weather_fog from "./weather/fog.svg?url"
import weather_fog_active from "./weather/fog_active.svg?url"
import weather_fog_white from "./weather/fog_white.svg?url"
import weather_overcast from "./weather/overcast.svg?url"
import weather_overcast_active from "./weather/overcast_active.svg?url"
import weather_overcast_white from "./weather/overcast_white.svg?url"
import weather_rain from "./weather/rain.svg?url"
import weather_rain_active from "./weather/rain_active.svg?url"
import weather_rain_white from "./weather/rain_white.svg?url"
import weather_sandstorm from "./weather/sandstorm.svg?url"
import weather_sandstorm_active from "./weather/sandstorm_active.svg?url"
import weather_sandstorm_white from "./weather/sandstorm_white.svg?url"
import weather_smog from "./weather/smog.svg?url"
import weather_smog_active from "./weather/smog_active.svg?url"
import weather_smog_white from "./weather/smog_white.svg?url"
import weather_snow from "./weather/snow.svg?url"
import weather_snow_active from "./weather/snow_active.svg?url"
import weather_snow_white from "./weather/snow_white.svg?url"
import weather_sprinkle from "./weather/sprinkle.svg?url"
import weather_sprinkle_active from "./weather/sprinkle_active.svg?url"
import weather_sprinkle_white from "./weather/sprinkle_white.svg?url"
import weather_sunny from "./weather/sunny.svg?url"
import weather_sunny_active from "./weather/sunny_active.svg?url"
import weather_sunny_white from "./weather/sunny_white.svg?url"
import weather_thunderstorm from "./weather/thunderstorm.svg?url"
import weather_thunderstorm_active from "./weather/thunderstorm_active.svg?url"
import weather_thunderstorm_white from "./weather/thunderstorm_white.svg?url"
import weather_tornado from "./weather/tornado.svg?url"
import weather_tornado_active from "./weather/tornado_active.svg?url"
import weather_tornado_white from "./weather/tornado_white.svg?url"
// BANK ICON
import bank_中信银行 from "./bank/中信银行.svg?url"
import bank_农业银行 from "./bank/农业银行.svg?url"
import bank_工商银行 from "./bank/工商银行.svg?url"
import bank_中国银行 from "./bank/中国银行.svg?url"
import bank_建设银行 from "./bank/建设银行.svg?url"
import bank_招商银行 from "./bank/招商银行.svg?url"
import bank_民生银行 from "./bank/民生银行.svg?url"
import bank_浦发银行 from "./bank/浦发银行.svg?url"
import bank_广发银行 from "./bank/广发银行.svg?url"
import bank_北京银行 from "./bank/北京银行.svg?url"
import bank_交通银行 from "./bank/交通银行.svg?url"
import bank_银行 from "./bank/银行.svg?url"
// OTHERS
// EOF
import EOF from "./icons/EOF.svg?url"
import EOF_dark from "./icons/EOF_dark.svg?url"
import content from "./icons/content.svg?url"
import content_white from "./icons/content_white.svg?url"
import content_md from "./icons/content_md.svg?url"
import content_md_white from "./icons/content_md_white.svg?url"
import clipboard from "./icons/clipboard.svg?url"
export default {
// LOGO
logo_icons: {
logo: logo,
logo_content: logo_content,
logo_content_saved: logo_content_saved,
logo_title: logo_title,
logo_title_saved: logo_title_saved,
logo_login: logo_login,
logo_register: logo_register,
logo_change_password: logo_change_password,
logo_avatar: logo_avatar,
},
// EOF
EOF: EOF,
EOFDark: EOF_dark,
// CONTENT
content: content,
content_white: content_white,
content_md: content_md,
content_md_white: content_md_white,
clipboard: clipboard,
tab_icons: {
invitation: tab_invitation,
add: tab_add,
back: tab_back,
close: tab_close,
delete: tab_delete,
done: tab_done,
doneSaved: tab_done_saved,
doneChanged: tab_done_changed,
edit: tab_edit,
menu: tab_menu,
recover: tab_recover,
search: tab_search,
bill: tab_bill,
billSimple: tab_bill_simple,
card: tab_card,
share: tab_share,
contentHide: tab_eye_close,
contentShow: tab_eye_open,
listSimple: tab_list_simple,
listDetail: tab_list_detail,
statistics: tab_statistics,
key: tab_key,
folder: tab_folder,
todo: tab_todo,
todoActive: tab_todo_active,
others: tab_others,
category: tab_category,
year: tab_year,
about: tab_about,
},
tab_icons_black: {
invitation: tab_invitation_black,
add: tab_add_black,
back: tab_back_black,
close: tab_close_black,
delete: tab_delete_black,
done: tab_done_black,
edit: tab_edit_black,
menu: tab_menu_black,
recover: tab_recover_black,
search: tab_search_black,
bill: tab_bill_black,
billSimple: tab_bill_simple_black,
card: tab_card_black,
share: tab_share_black,
contentHide: tab_eye_close_black,
contentShow: tab_eye_open_black,
listSimple: tab_list_simple_black,
listDetail: tab_list_detail_black,
statistics: tab_statistics_black,
key: tab_key_black,
folder: tab_folder_black,
todo: tab_todo_black,
todoActive: tab_todo_active_black,
others: tab_others_black,
category: tab_category_black,
year: tab_year_black,
about: tab_about_black,
},
weather_icons: {
cloudy: weather_cloudy,
cloudy_active: weather_cloudy_active,
cloudy_white: weather_cloudy_white,
fog: weather_fog,
fog_active: weather_fog_active,
fog_white: weather_fog_white,
overcast: weather_overcast,
overcast_active: weather_overcast_active,
overcast_white: weather_overcast_white,
rain: weather_rain,
rain_active: weather_rain_active,
rain_white: weather_rain_white,
sandstorm: weather_sandstorm,
sandstorm_active: weather_sandstorm_active,
sandstorm_white: weather_sandstorm_white,
smog: weather_smog,
smog_active: weather_smog_active,
smog_white: weather_smog_white,
snow: weather_snow,
snow_active: weather_snow_active,
snow_white: weather_snow_white,
sprinkle: weather_sprinkle,
sprinkle_active: weather_sprinkle_active,
sprinkle_white: weather_sprinkle_white,
sunny: weather_sunny,
sunny_active: weather_sunny_active,
sunny_white: weather_sunny_white,
thunderstorm: weather_thunderstorm,
thunderstorm_active: weather_thunderstorm_active,
thunderstorm_white: weather_thunderstorm_white,
tornado: weather_tornado,
tornado_active: weather_tornado_active,
tornado_white: weather_tornado_white,
},
bank_icons: {
中信银行: bank_中信银行,
农业银行: bank_农业银行,
工商银行: bank_工商银行,
中国银行: bank_中国银行,
建设银行: bank_建设银行,
招商银行: bank_招商银行,
民生银行: bank_民生银行,
浦发银行: bank_浦发银行,
广发银行: bank_广发银行,
北京银行: bank_北京银行,
交通银行: bank_交通银行,
银行: bank_银行,
}
}
4. 使用
这样就很方便了,跟上面第二步一样,又有代码提示,又可以使用 img
的属性自由变换大小。
代码提示
还是上面第三步的那个场景,但现在已经有代码提示了:
<div class="logo-wrapper">
<div :class="['logo', {valid: avatarLink} ]">
<img v-if="avatarLink" :src="avatarLink" alt="Diary Logo">
<img v-else :src="SVG_ICONS.logo_icons.logo_login" alt="LOGIN-LOGO">
</div>
</div>
动态渲染
其实主要是为了动态渲染,根据条件显示不同图标,可以由数据生成带图标的列表。这种操作是静态那种无法实现的。
比如通过天气图标的列表信息,生成带图标的可以选择天气的选择器。
const WeatherArray = [
{value : 'sunny' , label : '晴'} ,
{value : 'cloudy' , label : '多云'} ,
{value : 'overcast' , label : '阴'} ,
{value : 'sprinkle' , label : '小雨'} ,
{value : 'rain' , label : '雨'} ,
{value : 'thunderstorm' , label : '暴雨'} ,
{value : 'snow' , label : '雪'} ,
{value : 'fog' , label : '雾'} ,
{value : 'tornado' , label : '龙卷风'} ,
{value : 'smog' , label : '雾霾'} ,
{value : 'sandstorm' , label : '沙尘暴'} ,
]
<div class="weather" @click="chooseWeather(item.value)" v-for="item in WeatherArray" :key="item.value">
<img
:src="weatherSelected === item.value?
SVG_ICONS.weather_icons[item.value + '_active'] :
SVG_ICONS.weather_icons[item.value]"
:alt="item.label"
:title="item.label">
</div>
它最终是这样的:
完整代码:
<template>
<div class="weather-selector">
<div class="weather" @click="chooseWeather(item.value)" v-for="item in WeatherArray" :key="item.value">
<img
:src="weatherSelected === item.value?
SVG_ICONS.weather_icons[item.value + '_active'] :
SVG_ICONS.weather_icons[item.value]"
:alt="item.label"
:title="item.label">
</div>
</div>
</template>
<script lang="ts" setup>
import SVG_ICONS from "../../../assets/icons/SVG_ICONS.ts";
import {ref, watch} from "vue";
import {WeatherArray} from "@/entity/Weather.ts";
const emit = defineEmits(['change'])
const props = defineProps({
weather: {
type: String,
default: 'sunny'
}
})
const weatherSelected = ref(props.weather)
watch(() => props.weather, () => {
weatherSelected.value = props.weather
})
watch(weatherSelected, () => {
emit('change', weatherSelected.value)
})
function chooseWeather(weatherName: string) {
weatherSelected.value = weatherName
}
</script>
<style lang="scss" scoped>
@import "./weather-selector";
</style>
五、完成
这样整个项目原来 svg 都能正常显示了,舒服。
这是我在 github 上正在修改的一个分支: Diary -> Vite,有兴趣的可以看看
https://github.com/KyleBing/diary/tree/vite