一個用于Vue的類似于字體的SVG圖標系統

有時在Vue應用程序中管理圖標的自定義集合很困難。圖標字體易于使用,但是要進行自定義,必須依靠第三方字體生成器,并且合并沖突可能很難解決,因為字體是二進制文件。
使用SVG文件可以消除這些痛點,但是如何確保它們同樣易于使用,同時又可以輕松添加或刪除圖標呢?
理想的圖標系統的外觀:
- 要添加圖標,只需將它們放入指定的
icons文件夾中。如果不再需要圖標,只需刪除它即可。 - 要在模板中使用robot.svg圖標,語法非常簡單
<svg-icon icon="rocket" />。 - 可以使用CSS
font-size和color屬性(就像圖標字體一樣)對圖標進行縮放和著色。 - 如果頁面上出現同一圖標的多個實例,則不會每次都重復SVG代碼。
- 無需編輯Webpack配置。
這是通過編寫兩個小的單文件組件來構建的。此實現有一些特定的要求,盡管許多向導可以針對其他框架和構建工具對該系統進行重新設計:
webpack:如果使用Vue CLI來搭建應用程序,則說明已經在使用webpack。- svg-inline-loader:這使可以加載所有SVG代碼并清理不需要的部分。繼續并
npm install svg-inline-loader --save-dev從終端運行開始。
SVG Sprite組件
為了滿足對頁面上每個圖標實例不重復SVG代碼的要求,需要構建一個SVG“sprite”。如果以前從未聽說過SVG sprite,請將其視為包含其他SVG的隱藏SVG。在需要顯示圖標的任何地方,都可以通過引用<use>標簽內的圖標ID來將其復制到sprite之外,如下所示:
<svg><use xlink:href="#rocket" /></svg>
這段代碼本質上就是<SvgIcon>組件的工作方式,但是要繼續就需要先創建該<SvgSprite>組件。這是整個SvgSprite.vue文件。
<!-- SvgSprite.vue -->
<template>
<svg width="0" height="0" style="display: none;" v-html="$options.svgSprite" />
</template>
<script>
const svgContext = require.context(
'!svg-inline-loader?' +
'removeTags=true' + // remove title tags, etc.
'&removeSVGTagAttrs=true' + // enable removing attributes
'&removingTagAttrs=fill' + // remove fill attributes
'!@/assets/icons', // search this directory
true, // search subdirectories
/\w+\.svg$/i // only include SVG files
)
const symbols = svgContext.keys().map(path => {
// get SVG file content
const content = svgContext(path)
// extract icon id from filename
const id = path.replace(/^\.\/(.*)\.\w+$/, '$1')
// replace svg tags with symbol tags and id attribute
return content.replace('<svg', `<symbol id="${id}"`).replace('svg>', 'symbol>')
})
export default {
name: 'SvgSprite',
svgSprite: symbols.join('\n'), // concatenate all symbols into $options.svgSprite
}
</script>
在模板中,我們的lone <svg>元素的內容綁定到$options.svgSprite。如果不熟悉$options它包含直接附加到我們的Vue組件的屬性。我們可以附加svgSprite到組件的data,但是我們真的不需要Vue為此設置響應性,因為我們的SVG加載器僅在構建應用程序時運行。
在腳本中,我們用于require.context檢索所有SVG文件,并在使用時清理它們。我們svg-inline-loader使用與查詢字符串參數非常相似的語法來調用并傳遞幾個參數。我將它們分成多行以使它們更易于理解。
const svgContext = require.context(
'!svg-inline-loader?' +
'removeTags=true' + // remove title tags, etc.
'&removeSVGTagAttrs=true' + // enable removing attributes
'&removingTagAttrs=fill' + // remove fill attributes
'!@/assets/icons', // search this directory
true, // search subdirectories
/\w+\.svg$/i // only include SVG files
)
我們在這里基本上要做的是清理位于特定目錄中的SVG文件,以(/assets/icons使它們處于良好的狀態以在需要的任何地方使用。
該removeTags參數剔除標簽,我們并不需要為我們的圖標,如不title和style。我們特別想刪除title標簽,因為這些標簽可能會導致不必要的工具提示。如果要在圖標中保留任何硬編碼的樣式,請添加removingTags=title作為附加參數,以便僅title刪除標記。
我們還告訴加載程序刪除fill屬性,以便fill稍后可以使用CSS 設置自己的顏色。您可能會想要保留自己的fill顏色。如果是這種情況,則只需刪除removeSVGTagAttrs和removingTagAttrs參數。
最后一個加載程序參數是SVG圖標文件夾的路徑。然后require.context,我們提供了另外兩個參數,以便它搜索子目錄并僅加載SVG文件。
為了將所有SVG元素嵌套在SVG精靈中,我們必須將它們從<svg>元素轉換為SVG <symbol>元素。這就像更改標簽并為每個標簽賦予唯一性一樣簡單id,然后從文件名中提取出唯一性。
const symbols = svgContext.keys().map(path => {
// extract icon id from filename
const id = path.replace(/^\.\/(.*)\.\w+$/, '$1')
// get SVG file content
const content = svgContext(path)
// replace svg tags with symbol tags and id attribute
return content.replace('<svg', `<symbol id="${id}"`).replace('svg>', 'symbol>')
})
我們如何處理這個<SvgSprite>組件?我們將其放置在我們頁面上的所有依賴它的圖標之前。我建議將其添加到App.vue文件的頂部。
<!-- App.vue -->
<template>
<div id="app">
<svg-sprite />
<!-- ... -->
圖標組件
現在,我們來構建SvgIcon.vue組件。
<!-- SvgIcon.vue -->
<template>
<svg class="icon" :class="{ 'icon-spin': spin }">
<use :xlink:href="`#${icon}`" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
icon: {
type: String,
required: true,
},
spin: {
type: Boolean,
default: false,
},
},
}
</script>
<style>
svg.icon {
fill: currentColor;
height: 1em;
margin-bottom: 0.125em;
vertical-align: middle;
width: 1em;
}
svg.icon-spin {
animation: icon-spin 2s infinite linear;
}
@keyframes icon-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(359deg);
}
}
</style>
這個組件要簡單得多。如前所述,我們利用<use>標記來引用精靈中的ID。那id來自我們組件的icon道具。
我spin在那里添加了一個道具,可以根據需要切換.icon-spin類作為動畫的可選位。例如,這對于加載微調器圖標可能很有用。
<svg-icon v-if="isLoading" icon="spinner" spin />
根據需要,可能需要添加其他道具,例如rotate或flip。您可以根據需要直接將類直接添加到組件中,而無需使用道具。
我們組件的大部分內容是CSS。除了旋轉動畫之外,大多數動畫都用于使我們的SVG圖標更像圖標字體。為了使圖標與文本基線對齊,我發現在大多數情況下都可以應用vertical-align: middle以及底部邊距為0.125em。我們還將fill屬性值設置為currentColor,這使我們可以像為文本一樣為圖標著色。
<p style="font-size: 2em; color: red;">
<svg-icon icon="exclamation-circle" /><!-- This icon will be 2em and red. -->
Error!
</p>
而已!如果要在應用程序中的任何位置使用圖標組件,而不必將其導入到需要它的每個組件中,請確保在main.js文件中注冊該組件:
// main.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon.vue'
Vue.component('svg-icon', SvgIcon)
// ...