Introduction & 前言
先說 Vue3 可以參考本篇紀錄
最近繼續在完成全端 Side Project 時剛好碰到需要引入 Icon 的情形,以往的專案都是使用 Fontawesome 的免費方案,但有時候需要使用到的 Icon 在上面找不到,需要再另外引入現成的 SVG 去使用,所以索性又乾脆直接研究一番怎麼直接引入 SVG,如果你也有相同目的也有興趣就往下看吧,這邊筆者會記錄幾種使用方式。
SVG(可縮放向量圖形) - Scalable Vector Graphics
- SVG 是一種圖型格式,SVG是一種基於可延伸標記式語言 XML(電腦所能理解的資訊符號,類似 HTML),用於描述二維向量圖形的圖形格式。SVG 由 W3C 制定,是一個開放標準。
有興趣可以參考這篇文章 設計師對 SVG 應該有的觀念 裡面介紹到了 SVG 的優點,包括 放大不會失真、可以做成動畫同時保有小檔案容量的優點 及 圖示集…等等。
超級前言
最近疫情大爆炸,一天前的報導 疫情即時/國內單日新增 180 例確診個案!雙北防疫升級到第三級疫情即時/國內單日新增 180 例確診個案!雙北防疫升級到第三級 已經到了單日新增 180 例。
最近經歷了停水停電又疫情,希望大家一起加油撐過這個難關,平日不能出門就在家讀讀書看看影片寫寫 Code 吧!
超級前言要先提醒大家 勤洗手、少外出、多戴口罩 之外,請大家下載 臺灣社交距離App,這是一款透過藍芽偵測附近是否有確診者的 APP,但放心全程不須填寫任何個人資料。
Summary & 摘要
因為這次專案使用 Vue3 + TS(TypeScript) 所以設定上有些許不一樣,但不會差異太多,如果您使用 Vue2 也不用擔心!
主要會介紹幾種引入方式:
<Img>
引入方式
Inline 引入方式
Component 引入方式
<Img>
引入方式
首先介紹最基本的 <Img>
引入方式,這個方式最簡單只需要在專案內引入圖片檔案,接著再放到圖片標籤裡即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <template> <!-- Vue3 不需要在用一個 Div 包住所有的 Div --> <img :src="tagIcon"> <img :src="profileIcon">
<!-- 或直接使用路徑> <img src="xxx.svg"> </template>
<script lang="ts"> import { defineComponent, ref } from "vue";
// Icon import tagIconUrl from '@/assets/images/icon/icon_tag_circle.svg'; import profileIconUrl from '@/assets/images/icon/icon_profile.svg';
export default defineComponent({ name: "MyIconComponent", setup() { const tagIcon = ref(tagIconUrl); const profileIcon = ref(profileIconUrl);
return { tagIcon, profileIcon, }; }, }); </script>
|
- 第一種方式的優點是快速,缺點是好幾個 SVG 要引好幾次,另外無法直接對 SVG 改變顏色。
Inline 引入方式
第二種方式更簡單,如果你仔細打開 SVG 的檔案會發現長得像下面這樣。
直接把這幾段程式碼貼入到你的檔案中即可出現。
1 2 3 4 5 6 7 8
| <template> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> <path fill="var(--ci-primary-color, currentColor)" d="M256,16C123.452,16,16,123.452,16,256S123.452,496,256,496,496,388.548,496,256,388.548,16,256,16ZM403.078,403.078a207.253,207.253,0,1,1,44.589-66.125A207.332,207.332,0,0,1,403.078,403.078Z" class="ci-primary"/> <rect width="40" height="40" x="152" y="200" fill="var(--ci-primary-color, currentColor)" class="ci-primary"/> <rect width="40" height="40" x="320" y="200" fill="var(--ci-primary-color, currentColor)" class="ci-primary"/> <path fill="var(--ci-primary-color, currentColor)" d="M338.289,307.2A83.6,83.6,0,0,1,260.3,360H251.7a83.6,83.6,0,0,1-77.992-52.8l-1.279-3.2H137.968L144,319.081A116,116,0,0,0,251.7,392H260.3A116,116,0,0,0,368,319.081L374.032,304H339.568Z" class="ci-primary"/> </svg> </template>
|
- 第二種方式雖然可以自由改變大小及顏色,但與第一種方式同樣的,需要引入好幾個 SVG 要貼入好幾次,而且這種方式會讓版面嚴重的變得雜亂,一點也不清爽。
Component 引入方式
最後是筆者最推薦的一種方式,這種方式日後擴充性高,使用方便,且可以任意放大放小不失真,加上想改變顏色就可以直接改變。
在了解這種方式之前要先來介紹一下 SVG 的標籤,基本上你必須認識最基本的三個標籤,分別為 <defs>
、<use>
、<symbol>
。
基本上 <svg>
你們已經認識了,還有包在 <svg>
裡面的 <path>
、<rect>
,這些就是定義圖形的長相,但為什麼還有另外上面三個呢?
這三個標籤 <defs>
、<symbol>
、<use>
可以使我們輕鬆達到把所有的 SVG 放在同一個 .svg 檔案裡,然後透過分別給予他們的 ID Name 去呼叫並使用它。
<defs>
標籤
用於預定義一个元素使其能夠在 SVG 圖像中重複使用,舉的例子:
1 2 3 4 5 6 7 8 9 10
| <svg> <defs> <symbol id="3d-svg" viewBox="0 0 512 512"> <path fill='var(--ci-primary-color, currentColor)' d='M68.983,382.642l171.35,98.928a32.082,32.082,0,0,0,32,0l171.352-98.929a32.093,32.093,0,0,0,16-27.713V157.071a32.092,32.092,0,0,0-16-27.713L272.334,30.429a32.086,32.086,0,0,0-32,0L68.983,129.358a32.09,32.09,0,0,0-16,27.713V354.929A32.09,32.09,0,0,0,68.983,382.642ZM272.333,67.38l155.351,89.691V334.449L272.333,246.642ZM256.282,274.327l157.155,88.828-157.1,90.7L99.179,363.125ZM84.983,157.071,240.333,67.38v179.2L84.983,334.39Z' class='ci-primary'/> </symbol> </defs>
<use xlink:href="#3d-svg" x="50" y="50" /> <use xlink:href="#3d-svg" x="200" y="550" /> </svg>
|
<symbol>
標籤
這個標籤通常會包在 <defs>
標籤裡面,用於定義可重複使用的符號,這邊要記得,不管使用 <defs>
或是 <symbol>
都不會直接顯示,必須使用最後一個要介紹的標籤。
<use>
標籤
透過 <use>
標籤可以使用上面定義那兩個標籤的 SVG,使用方法如上面所示:
1 2
| <use xlink:href="#3d-svg" x="50" y="50" /> <use xlink:href="#3d-svg" x="200" y="550" />
|
切記 ID 名稱一定要對到才有用。
講解完這幾個標籤你大概就知道該怎麼做成 Component,一開始筆者不知道還以為是把所有要用到的 SVG 都引入到那個 Component 裡面再去呼叫,這樣也太不清爽了。
- 把所有要用到的 SVG 分類好分成幾個檔案放,然後再透過 Webpack 的 require.context 遍歷 SVG 把他們都寫入 main.js 裡面,最後透過 <use>
的標籤寫在 Vue Component 裡面,透過帶入 Prop 去呼叫你要使用的 Icon。
*這邊使用 Coreui Free Admin 的免費 Icon 做範例*
- 建立 Icon 的相關資料夾
SVG 的範例檔案在步驟一的下方有附上,雖然是提供 Coreui Free Admin 的免費 Icon,但還是請不要使用在商業用途,以避免法律責任。
首先到 /src/assets/
建立 Icons 資料夾,這邊筆者把 Icons 底下資料夾在分為 sprites 及 svg 兩個,前者會放所有 SVG 的程式碼內容,像是這種程式碼 <path fill='var(--ci-primary-color, currentColor)' d='M68.983,382.642l171.35,98.928a32.082,32.082,0,0,0,32,0l171.352-98.929a32.093,32.093,0,0,0,16-27.713V157.071a32.092,32.092,0,0,0-16-27.......略
,然後用 及 包起來。
這種所有 SVG 的設定組合集可以不一定只能有一隻檔案,筆者這邊就分為三個檔案,分別為 LOGO、國旗、其他 SVG 分類。
檔案範例請 **點我下載**,密碼為 5u7r,如果連結過期或不能使用請在下方留言告訴我。
- 安裝 svg-sprite-loader 套件
這是讓 Webpack 讀懂你 SVG 的套件,如果沒安裝就會噴錯。
1 2 3 4 5
| // NPM npm i svg-sprite-loader -S
// YARN yarn add svg-sprite-loader
|
選擇你的安裝方式,安裝後記得在 Vue.config.js 裡面加上設定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| const path = require('path');
function resolve(dir) { return path.join(__dirname, '.', dir) }
module.exports = { configureWebpack: { module: { rules: [{ test: /\.svg$/, use: ['svg-sprite-loader'], }, ] }, }, chainWebpack: config => { config.module.rules.delete("svg"); config.module .rule('svg-sprite-loader') .test(/\.svg$/) .include .add(resolve('src/main.ts')) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: '[name]' }) }, };
|
正常的網路上教學都只會爬文到加入 chainWebpack 這個設定,但似乎都沒有加上 configureWebpack 這個設定,這邊筆者也不太確定為什麼加上就好了,如果有哪路大神知道還請不吝設在下方留言,感激不盡。
如果你是使用 Vue Cli 記得設定完要重新在啟動一次,設定檔才會被讀到!
- 將所有設定組合集包在 HTML 裡
聰明的你會看到在第一步 建立 Icon 的相關資料夾 圖片裡有多一個 index.ts 的檔案,如果你並不是使用 TS 的話可以創建一個 index.js。
裡面的目的是要遍歷你的 SVG 組合設定集內容並把他們輸出到 HTML 上:
1 2 3 4 5 6 7
|
const req = require.context("@/assets/icons/sprites", false, /\.svg$/); const requireAll = (requireContext: any) => requireContext.keys().map(requireContext); requireAll(req);
|
這邊可以引入單一隻 SVG 或是組合設定集,但相信大部分的人都是選後者;之後回去頁面上打開開發者模式(F12)看應該就會看到引入的組合設定集。
展開後會發現熟悉的標籤 <defs>
及 <symbol>
出現了。
- 建立方便引用的 Component
這邊就是重頭戲了,在這邊我們需要建立一個 Icon 的 Component。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| <template lang="pug"> svg.svgIcon( xmlns="http://www.w3.org/2000/svg" :class="className" aria-hidden="true" ) use( :xlink:href="`#${iconType}_${iconFullName}`" ) </template>
<script lang="ts"> import { defineComponent, toRef } from "vue";
type iconTitleType = string;
export default defineComponent({ name: "IconComponent", props: { className: { type: String, }, iconType: { type: String, default: "free", }, iconName: { type: String, default: "3d", }, }, setup(props) { let iconType = toRef(props, "iconType"); let iconName = toRef(props, "iconName"); let iconFullName: iconTitleType; switch (iconType.value) { case "brand": iconFullName = `cib-${iconName.value}`; break; case "flag": iconFullName = `cif-${iconName.value}`; break; case "free": default: iconFullName = `cil-${iconName.value}`; break; }
return { iconFullName, ...props, }; }, }); </script>
|
仔細看看剛剛步驟三展開引入的 SVG 組合設定集,裡面的 SVG ID 開頭都會跟著我們分類的檔案名稱,所以這邊也必須判斷你要使用的 SVG 在哪一個分類,所以特別使用 iconType、iconName 去判斷,並從父層透過 prop 傳過來。
如果你沒有使用 TS 其實也是一樣做法,最終目的都是想要透過父層傳過來的 prop 去判斷要使用哪一個 Icon。
- 全局註冊 Component
到這邊幾乎就快大功告成了,到入口 main.ts 或是 main.js(沒有用 TS 的話)。
這邊註冊分為 Vue2 及 Vue3:
1 2 3 4 5
| import Vue from 'vue' import "@/assets/icons"; import SvgComponent from "@/components/SvgComponent.vue";
Vue.component('SvgComponent', SvgComponent)
|
在 Vue3 已經遺棄 Vue.component() 這種方法了。
1 2 3 4 5 6 7 8
| import "@/assets/icons"; import SvgComponent from "@/components/SvgComponent.vue";
createApp(App) .component("SvgComponent", SvgComponent) .use(store) .use(router) .mount("#app");
|
請記得 mount 一定要在最後。
- Enjoy It!
最後就是開心地使用了,由於全局註冊的關係,也不用每一個檔案都要在引入一次,直接使用即可。
1 2 3 4 5
| <template lang="pug"> SvgIcon( iconName="happy" ) <template>
|
- 擴充方法
到這個步驟你已經可以順利的引入 SVG,如果你有自定義的 SVG 請繼續參考這個步驟。
在這邊我們先改寫剛剛的 步驟四 的 建立方便引用的 Component - Icon 的 Component,將 setup 裡面判斷目前要用哪一個 SVG 的地方改寫如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| switch (iconType.value) { case "custom": iconFullName = `c-${iconName.value}`; break; case "brand": iconFullName = `cib-${iconName.value}`; break; case "flag": iconFullName = `cif-${iconName.value}`; break; case "free": default: iconFullName = `cil-${iconName.value}`; break; }
|
接著在按照 步驟一 在資料夾 ./src/assets/icons/sprites
裡面新增 custom.svg
,然後裡面輸入下列程式碼(這邊可以複製筆者的,會有驚喜):
1 2 3 4 5 6 7 8 9 10 11
| <svg aria-hidden="true" style="position: absolute; width: 0; height: 0; overflow: hidden;" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <symbol id="c-test-svg"> // 這邊為可替換的內容 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 521.84 201"><defs><style>.cls-1{fill:#040000;}.cls-1,.cls-3{stroke:#231815;stroke-miterlimit:10;}.cls-2{font-size:90px;fill:#fff;}.cls-2,.cls-4{font-family:HanziPenTC-W5-B5pc-H, HanziPen TC;}.cls-3{fill:#e5852e;}.cls-4{font-size:70px;fill:#231815;}</style></defs><title>資產 2</title><g id="圖層_2" data-name="圖層 2"><g id="圖層_1-2" data-name="圖層 1"><rect class="cls-1" x="0.5" y="0.5" width="520.84" height="200"/><text class="cls-2" transform="translate(20.5 127.41)">Happy</text><rect class="cls-3" x="262.19" y="25.22" width="243.01" height="152.69" rx="12"/><text class="cls-4" transform="translate(321.67 122.5)">SVG</text></g></g></svg> </symbol> <symbol id="請輸入你的客製化ID"> // 新增更多你的 SVG 吧 </symbol> </defs> </svg>
|
接著依照上面 步驟三 的 將所有設定組合集包在 HTML 裡,會把所有 ./src/assets/icons/sprites
下的 SVG 程式碼都寫進 HTML 裡面,我們只需要透過 步驟四 的 建立方便引用的 Component 去把這個 SVG 呼叫出來即可。
1 2 3 4 5 6 7 8 9 10 11 12
| <template lang="pug"/>
SvgIcon( className="tw-mr-1", :width="100", :height="40", iconType="custom", iconName="test-svg", ) </template>
|
Conclusion & 結論
這次因為 Vue3 加上 TS 的關係又吸取了很多知識,雖然 Side Project 的進度緩慢,但許多新知吸取後覺得滿滿成就感,很多原本是簡單的東西,例如 SVG 的引入,但可以在一件事情上學習且練習到其他關聯的東西,這才是最無價的。
希望這篇文章可以幫助自己之後需要再次使用時可以快速回憶,也希望能幫助到各位,最後希望最近的疫情可以趨緩,我們可以一起度過這個困難的時期,再次提醒,大家要記得 勤洗手、少外出、一定要戴口罩!
參考網站