前言
網(wǎng)頁圖標(biāo)展示方式大概可以分為以下幾類
- img 標(biāo)簽率翅。最原始的方式,不支持改變顏色
- css sprites袖迎。其實質(zhì)還是使用img標(biāo)簽進(jìn)行展示
- svg 標(biāo)簽冕臭。可以改變顏色燕锥,使代碼臃腫辜贵,不易維護(hù)「蓿可使用 svg-inline-loader念颈、vue-svg-loader(目前ant-design-vue使用該方式) 進(jìn)行優(yōu)化
- font 圖標(biāo)。常用的生成字體圖標(biāo)的工具:IconFont连霉、IcoMoon
- SVG Sprites。本文重點推薦嗡靡,IconFont 推薦使用該方式跺撼,實質(zhì)是對svg標(biāo)簽展示方式進(jìn)行優(yōu)化,參考文章:未來必?zé)幔篠VG Sprites技術(shù)介紹讨彼、svg-sprite-loader 使用教程
前端工程化 - 工程配置 vue-cli3+
在了解 SVG Sprites
技術(shù)之后歉井,引出本文的主角:svg-sprite-loader 這是關(guān)鍵
1、雛形
vue-cli
內(nèi)置的webpack配置會將svg文件使用 file-loader
進(jìn)行加載哈误,這不符合我們當(dāng)前的需求
- 解決方案:固定目錄下的svg文件使用
svg-srpite-loader
進(jìn)行加載哩至,其它則繼續(xù)使用file-loader
,目前網(wǎng)上搜索到的解決方案基本如此
// vue.config.js
const path = require('path');
function Resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
chainWebpack: config => {
config.module.rule('svg').exclude.add(Resolve('src/assets/icons')); //讓file-loader排除掉這個目錄
config.module.rule('icons').test(/\.svg$/).include.add(Resolve('src/assets/icons')).end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon-[name]',
}).end()
//使用svgo-loader 進(jìn)行優(yōu)化蜜自,去除svg本身的填充色
.use('svgo-loader').loader('svgo-loader').options({
plugins: [
{removeAttrs: {attrs: 'path:fill'}},
{ removeXMLNS: true }
]
})
}
}
// main.vue
<template>
<svg>
<use :xlink:href="'#'+iconId"></use>
</svg>
</template>
<script>
import MySvg '@/assets/icons/logo.svg'; // src/assets/icons/logo.svg
export default {
data() {
return {
iconId: MySvg.default.id
}
}
}
</script>
這樣做有一個問題菩貌,如果圖標(biāo)是頁面專屬,其它頁面不會用到重荠,把svg都放在同一個目錄下箭阶,沒有跟隨組件目錄,那這樣就非常不好管理。
2仇参、優(yōu)化
在vue-cli的官方文檔中看到這么一段話:
不再局限于 src/assets/icons
目錄下嘹叫,而是判斷文件名以 .icon.svg
結(jié)尾:
// vue.config.js
const path = require('path');
function Resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
chainWebpack: config => {
//svg sprites
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
svgRule.oneOf('icon').test(/\.icon\.svg$/)
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon_[hash:base64:5]',
}).end()
.use('svgo-loader').loader('svgo-loader').options({
plugins: [
{removeAttrs: {attrs: 'path:fill'}},
{ removeXMLNS: true }
]
}).end()
//普通svg圖片
svgRule.oneOf('img').test(/\.svg$/)
.use('file-loader')
.loader('file-loader').options({
name: 'img/[name].[hash:8].[ext]'
})
}
}
3、和 import
诈乒、require
說再見吧
經(jīng)過上面的優(yōu)化后罩扇,在svg文件引入的方式上還是有些不盡人意,有沒有像 img
標(biāo)簽?zāi)菢邮娣氖褂梅绞侥嘏履ィ慨?dāng)然有暮蹂!首先看看在基于vue-cli
腳手架的項目中是怎么實現(xiàn):
處理靜態(tài)資源
當(dāng)然,這需要
vue-loader
處理癌压,詳細(xì)文檔修改vue-loader后的最終配置:
const path = require('path');
function Resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = {
chainWebpack: config => {
//svg sprites
const svgRule = config.module.rule('svg')
svgRule.uses.clear()
svgRule.oneOf('icon').test(/\.icon\.svg$/)
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({
symbolId: 'icon_[hash:base64:5]',
}).end()
.use('svgo-loader').loader('svgo-loader').options({
plugins: [
{removeAttrs: {attrs: 'path:fill'}},
{ removeXMLNS: true }
]
}).end()
//普通svg圖片
svgRule.oneOf('img').test(/\.svg$/)
.use('file-loader')
.loader('file-loader').options({
name: 'img/[name].[hash:8].[ext]'
})
//vue-loader
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
// 修改它的選項...
options.transformAssetUrls = {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href'],
'vc-svg-icon': 'src' //這一項是關(guān)鍵仰泻,下面的組件封裝有用到
}
return options
})
}
}
上面的 options.transformAssetUrls['vc-svg-icon'] = 'src'
,讓vue-loader
識別vc-svg-icon
組件的src
屬性滩届,并對其解析為一個 模塊依賴
4集侯、組件封裝
<template>
<svg class="vc-svg-icon" aria-hidden="true" :icon-href="href">
<use :xlink:href="href"></use>
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
src: module
},
computed: {
href() {
return `#${this.src.default.id}`
}
},
mounted() {
//初始化時查找是否存在該 symbol
const container = document.querySelector('#__SVG_SPRITE_NODE__')
if (!container.querySelector(this.href)) {
container.insertAdjacentHTML('beforeend', this.src.default.content)
}
},
destroyed() {
//組件銷毀后查找是否存在無用的 symbol,沒有被vc-svg-icon組件引用則移除對應(yīng)的symbol
const hasIcon = document.querySelector(`[icon-href="${this.href}"]`)
const symbol = document.querySelector('#__SVG_SPRITE_NODE__').querySelector(this.href)
if (!hasIcon && symbol) {
symbol.remove()
}
}
}
</script>
<style>
.vc-svg-icon {
width: 1em;
height: 1em;
vertical-align: middle;
fill: currentColor;
overflow: hidden;
}
</style>
mounted
和 destroyed
生命周期里的處理是為了“用完即銷毀”帜消,可自行斟酌棠枉,非必要,因為symbol標(biāo)簽的父級svg#__SVG_SPRITE_NODE__
是隱藏的(display:none
)泡挺,大量存在無用的symbol標(biāo)簽也不會造成性能問題
5辈讶、使用
//*.vue
<template>
<div>
<!-- 一般使用 -->
<vc-svg-icon src="./***/***.icon.svg"></vc-svg-icon>
<!-- src 值在 data 中 -->
<vc-svg-icon :src="myIcon"></vc-svg-icon>
</div>
</template>
<script>
export default {
data() {
return {
myIcon: require('./***/***.icon.svg')
}
}
}
</script>