看不下去了吗?我对图标组件做了这样的改造....

1,145 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

字体图标在项目使用时,是否需要做组件封装?还是要考虑实用性以及是否能够提高开发效率。

以下是对组件的二次升级。记录一下封装过程中遇到的问题及解决办法。

字体图标库

石器时代:

之前大部分的项目中对图标的展现,都是单图片jpg、png,因为其使用简单快捷,img标签或者容器背景图引用即可,这个时候我们的目的就是“快”

image.png

css


background-image: url(img/1.jpg);
background-size: 450px;/*设置图片大小*/
...

烦躁吗....

蒸汽时代:

随着图片直接引用,发现巨石应用时代的来临,资源加载越来越多,页面也越来越慢,显示体验也不好。这个时候,就过渡到精灵图也叫雪碧图时代

就是一张大图,把各个小图片汇聚到一起,然后通过css的定位进行提取显示.

image.png

css

 .box1 li:nth-child(1) {
    width: 30px;
    height: 30px;
    background: url(./images/map-icon.png) no-repeat 0 0; /* 第一个图标*/
 }

这个时候相对来说,解决了页面加载的问题,显示也快了很多,流畅了很多。

项目中使用的也挺好,但是开发人员发现一个问题,添加一个图片需要UI设计人员,加一下。如果大小调整或者位置调整,那就比较麻烦了,当然也有一些工具的产生来变相解决这个问题。我们之前就用过 CssSprite.exe

image.png

但是结果还是感觉,“挺麻烦”

电子信息时代:

这个时候相对就高级了许多,因为字体库相对于文字的应用和图标的使用,产生了一些,线性图标、面性图标、线面结合类,而且加载也比较方便,就类似引用脚本一样。

下来我们看看如何使用

图标库准备:

阿里巴巴矢量库,阿里的平台,对于png之类的图标,可以转换为字体库,然后导出成对应的字体库,内含CSS、JS加载方式,支持低版本浏览器,以及SVG加载使用。

1、这种工作可以和公司的产品、UI进行沟通,如果项目或者平台上有图标更新,需要他们及时知会。
2、下载下来后,开发人员需要本地处理后,进行提交使用。

image.png 下载本地后的文件:

image.png

demo_index.html

image.png

相对来说使用非常简单,你会发现换一个公司在换一个,大家都在用,不亦乐乎。

项目引用

项目入口静态文件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-classclassName即可,

  • 字体图标的地址名称
  • 添加字体图标容器样式

使用:

<svg-icon icon-class="icon-sousuo"</svg-icon>

想加额外样式了,添加className即可

效果图:

image.png

那么问题来了

1、如果需要控制间距,防止图标与文字之间拥挤到一起,我们能不能默认在样式中写死样式间距呢?
image.png

2、如果是一个svg的图片,能不能适配加载?为什么这么想?image标签加载不香吗?

image.png

回答

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;
}

image.png

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="listeners",我们记录下,一般使用组件的时候,都会使用vbind="listeners",我们记录下,一般使用组件的时候,都会使用v-bind="attrs" 和 v-on="$listeners

v-bind="$attrs" v-on="$listeners" 用于当vue中有多层组件嵌套, 且多层组件间需要相互传递数据。

  • v-bind="$attrs" 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。

  • v-on="$listener 使孙子组件接收爷爷组件传递的事件

升级完成,简单的东西,被我写出了大片的感觉~~~~

来吧,点赞吧~