携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情
字体图标在项目使用时,是否需要做组件封装?还是要考虑实用性以及是否能够提高开发效率。
以下是对组件的二次升级。记录一下封装过程中遇到的问题及解决办法。
字体图标库
石器时代:
之前大部分的项目中对图标的展现,都是单图片jpg、png,因为其使用简单快捷,img标签或者容器背景图引用即可,这个时候我们的目的就是“快”
css
background-image: url(img/1.jpg);
background-size: 450px;/*设置图片大小*/
...
烦躁吗....
蒸汽时代:
随着图片直接引用,发现巨石应用时代的来临,资源加载越来越多,页面也越来越慢,显示体验也不好。这个时候,就过渡到精灵图也叫雪碧图时代
就是一张大图,把各个小图片汇聚到一起,然后通过css的定位进行提取显示.
css
.box1 li:nth-child(1) {
width: 30px;
height: 30px;
background: url(./images/map-icon.png) no-repeat 0 0; /* 第一个图标*/
}
这个时候相对来说,解决了页面加载的问题,显示也快了很多,流畅
了很多。
项目中使用的也挺好,但是开发人员发现一个问题,添加一个图片需要UI设计人员,加一下。如果大小调整或者位置调整,那就比较麻烦了,当然也有一些工具的产生来变相解决这个问题。我们之前就用过 CssSprite.exe
但是结果还是感觉,“挺麻烦”
电子信息时代:
这个时候相对就高级了许多,因为字体库相对于文字的应用和图标的使用,产生了一些,线性图标、面性图标、线面结合类
,而且加载也比较方便,就类似引用脚本一样。
下来我们看看如何使用
图标库准备:
阿里巴巴矢量库,阿里的平台,对于png之类的图标,可以转换为字体库,然后导出成对应的字体库,内含CSS、JS加载方式,支持低版本浏览器,以及SVG加载使用。
1、这种工作可以和公司的产品、UI进行沟通,如果项目或者平台上有图标更新,需要他们及时知会。
2、下载下来后,开发人员需要本地处理后,进行提交使用。
下载本地后的文件:
demo_index.html
相对来说使用非常简单,你会发现换一个公司在换一个,大家都在用,不亦乐乎。
项目引用
项目入口静态文件CDN方式:
<!-- 载入样式-字体库 -->
<link type="text/css" rel="stylesheet" href="<%= BASE_URL %>resource/fonts/iconfont.css" charset="utf-8" />
<!-- 载入第三方工具库-字体库 -->
<script type="text/javascript" src="<%= BASE_URL %>resource/fonts/iconfont.js"></script>
入口文件 main.js引用
import '@/assets/font/iconfont.css'
大部分项目使用都在main.js里面使用,这种方式比较通用,比较直接。
注意:
如果大型项目,从系统架构层面考虑以及性能,对字体图标会归类到静态资源类,适合CDN引入。
上述操作,相对整体来说比较简单,我们下来说以下核心的组件层封装,提供日常开发使用。
组件封装:
<template>
<svg :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script setup>
import { computed } from "vue"
const props = defineProps({
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
},
})
// 图标在 iconfont 中的名字
const iconName = computed(() => {
return `#${props.iconClass}`
})
// 给图标添加上类名
const svgClass = computed(() => {
if (props.className) {
return `svg-icon ${props.className}`
}
return 'svg-icon'
})
</script>
<style scoped lang="scss">
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
上述代码中,思路比较简单,就是开发人员在使用时,传入icon-class
和 className
即可,
- 字体图标的地址名称
- 添加字体图标容器样式
使用:
<svg-icon icon-class="icon-sousuo"</svg-icon>
想加额外样式了,添加className即可。
效果图:
那么问题来了
1、如果需要控制间距,防止图标与文字之间拥挤到一起,我们能不能默认在样式中写死样式间距呢?
2、如果是一个svg的图片,能不能适配加载?为什么这么想?image标签加载不香吗?
回答
1、间距问题,可以在字体图标上添加className
来解决,方式比较多,但是为了一行间距样式,在className或者样式里面加,多少感觉有点不太值的。组件上,访问的时候能不能这样控制,简单
<svg-icon icon-class="icon-xinzeng" space="5"></svg-icon>
2、关于svg图片,因为字体图标都是svg加载,项目中的静态资源可归为一类,用一个图片组件容器加载,也说的过去,这样使用起来,统一方便。
升级版
<template>
<div v-if="isExternal" :style="styleExternalIcon" :class="[spaceClass]" class="svg-external-icon svg-icon"
v-on="$listeners" />
<span v-else>
<svg :class="[svgClass, spaceClass]" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
<slot></slot>
</span>
</template>
<script setup>
import { computed } from "vue"
const props = defineProps({
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
},
space: {
type: String,
default: ''
},
})
function isLink(path) {
return path.indexOf('/') !== -1
}
// 图标在 iconfont 中的名字
const isExternal = computed(() => {
return isLink(props.iconClass)
})
// 图标在 iconfont 中的名字
const iconName = computed(() => {
return `#${props.iconClass}`
})
// 给图标添加上类名
const svgClass = computed(() => {
if (props.className) {
return `svg-icon ${props.className}`
}
return 'svg-icon'
})
// 给图标添加间距
const spaceClass = computed(() => {
if (props.space) {
return `svg-spce-${props.space}`
}
return ''
})
const styleExternalIcon = computed((params) => {
return {
mask: `url(${props.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%`
}
})
</script>
<style scoped lang="scss">
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
.svg-spce-5 {
padding: 0 5px;
}
.svg-spce-10 {
padding: 0 10px;
}
</style>
核心解读:
1、props,添加了space的属性
内容做了spaceClass的处理,根据props.space添加的结果来进行拼接
// 给图标添加间距
const spaceClass = computed(() => {
if (props.space) {
return `svg-spce-${props.space}`
}
return ''
})
.svg-spce-5 {
padding: 0 5px;
}
.svg-spce-10 {
padding: 0 10px;
}
2、适配svg图片
function isLink(path) {
return path.indexOf('/') !== -1
}
// 图标在 iconfont 中的名字
const isExternal = computed(() => {
return isLink(props.iconClass)
})
const styleExternalIcon = computed((params) => {
return {
mask: `url(${props.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${props.iconClass}) no-repeat 50% 50%`
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover !important;
display: inline-block;
}
判断是否是path类型,通过isLink进行判断,如果是,html使用
<div v-if="isExternal"
:style="styleExternalIcon"
:class="[spaceClass]"
class="svg-external-icon svg-icon"
v-on="$listeners" />
用到了 v-on="attrs" 和 v-on="$listeners
v-bind="$attrs" v-on="$listeners"
用于当vue中有多层组件嵌套, 且多层组件间需要相互传递数据。
-
v-bind="$attrs"
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。 -
v-on="$listener
使孙子组件接收爷爷组件传递的事件
升级完成,简单的东西,被我写出了大片的感觉~~~~
来吧,点赞吧~