一个用于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)
// ...