SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader

SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader_SVG

〇、前言:

本篇将介绍:

  • 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 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader_vite_02

将所有 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 可以直接在 imgsrc 中引用 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 中不能用就是了。

SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader_vue3_03

那就需要另找方法了,试过使用 component 的方式等等,不是我喜欢的方式,另外在控制它大小的时候不会控制了。
最终还是找到了这个 vite-svg-loaderurl 数据的方式引入 svg 文件内容。

相关阅读:

说下过程:

1. 添加 vite-svg-loader

yarn 或 npm

yarn add vite-svg-loader
# 或者
npm i vite-svg-loader

2. 在 vite.config.ts 中添加 svgLoader()

  1. 打开 vite.config.ts 文件
  2. 引入 svgLoader import svgLoader from "vite-svg-loader"
  3. 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"

SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader_vue3_04


我特别喜欢批量修改这种配置文件:

SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader_vue3_05

完整代码是这样的:

// 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 的属性自由变换大小。

代码提示
还是上面第三步的那个场景,但现在已经有代码提示了:

SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader_tornado_06

<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>

它最终是这样的:

SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader_SVG_07

完整代码:

<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

SVG 文件的引入方式之一:以 URL 的方式引入 SVG 文件,vue2、vue3+Vite vite-svg-loader_SVG_08