自己写一个svg图标组件吧

1,768 阅读5分钟

我正在参加「掘金·启航计划」

亲爱的UI设计师,在设计页面样式(看着好像一样但又不一样😂),组件总有结构一样,颜色总不一样。你能怎么办?

image.png

每次都添加新的png到文件中?

这个不可能啊!!!!,这种东西还是不要再添加新的png文件了。

都现在了,都来感受一下svg组件的魅力吧。

为什么要使用svg组件?

答:因为svg很方便、很快乐,它能大能小不模糊,它能红能绿也能黄,那么多优点,让我去选择svg,可是如果每一个svg都需要去引入重写,岂不是很麻烦,所以封成组件进行展示,才是优选。

svg简介

SVG 是一种基于 XML 语法的图像格式,全称是可缩放矢量图(Scalable Vector Graphics)。

SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。

svg在webpack中使用

用开源的前端打包工具webpack,去使用svg。

npm包

安装:svg-sprite-loader

作用:使个体的svg,去构建一个类似大型的svg雪碧图。

安装:svgo-loader

作用:去除svg 自带的 fill 属性给清除掉。

配置

如何配置就不多说,网上还是有很多关于在webpack中的使用方法,这篇文章主要讲述svg在vite中的使用。

为大家找到了一些相关配置文章地址:

1、使用 svg-sprite-loader、svgo-loader

2、vue中使用webpack添加svg以及去除fill

就算是webpack的配置,下面的组件化svg还是对你有点用的,耐心向下看看。

svg在vite中使用

用新型的前端构建工具vite,去使用svg。

npm包

安装:vite-plugin-svg-icons

作用:去构建生成 svg 画面映射(雪碧图)。

tips:如果需要请先看一下文档:GitHub - vbenjs/vite-plugin-svg-icons: Vite Plugin for fast creating SVG sprites.,文档将使用方法说的很清楚,我这边也就拿来做一些必要处理。

安装:___暂无____

作用:自动去除svg中属性fill(先占位)。

tips:可惜的是我没能找到,像webpack插件那样自动化消除掉项目文件下的svg中fill属性的方法,目前我还是使用手动去删除掉svg的fill属性。

怎么个手动法?

引入svg文件,在VScode等编辑器中打开,使用ctrl+F搜索fill 直接把fill="#CCC"删除,效果如下

// ... 是我省略的数据
<svg ... ><path d="..." p-id="2526" fill="#1afa29"></path></svg>

删除fill属性

// ... 是我省略的数据
<svg ... ><path d="..." p-id="2526"></path></svg>

嗯,放在文件下即可了,到时候我们传什么颜色,他就是什么颜色。

配置

在vite中配置

import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
defineConfig({
  plugins: [
      createSvgIconsPlugin({
        // 指定需要缓存的图标文件夹
        iconDirs: [resolve(process.cwd(), "./src/assets/svgs")],
        // 指定symbolID的格式
        symbolId: "icon-[name]"
      })
    ],
})

使用

都差不多啊,这里使用的是vue3,改一下vue2也能用的。

创建组件

<script setup>
import { computed, toRefs } from "vue";
const props = defineProps({
  name: {
    type: String,
    required: true
  },
  color: {
    type: String,
    default: ""
  }
});
const { name, color } = toRefs(props);
const iconName = computed(() => {
  return `#icon-${name.value}`;
});
const svgClass = computed(() => {
  if (name.value) return `svg-icon icon-${name.value}`;
  return "svg-icon";
});
</script><template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" :fill="color"/>
  </svg>
</template><style scoped>
  .svg-icon {
    width: 1em;
    height: 1em;
    vertical-align: middle;
    fill: currentColor;
    object-fit: contain;
  }
</style>

解析:

为什么使用em的单位?

em:相对长度单位规定相对于另一个长度属性的长度。相对长度单位在不同渲染介质之间缩放表现得更好,而且因为1em可以很好与组件font-size大小相照应,em是看父级的font-size大小,这不就刚刚好就控制住了svg的width与height大小。

也就是说,在引入组件,给予font-size就可以修改svg的高与宽了。

全局使用

// 使用svg组件
import "virtual:svg-icons-register";
import SvgIcon from "@/components/Icon/SvgIcon.vue";
​
const app = createApp(App);
app.component("svg-icon", SvgIcon);

解析:

为什么使用:virtual:svg-icons-register

virtual:XXX:在vite中的称为虚拟模块的引入。

详情看:插件 API | Vite 官方中文文档 (vitejs.dev),点击传送到相关位置。

使用

<svg-icon class="icon" name="xingxing" color="#7bd57b"/>

解析:

svg-icon组件为什么不将font-size写行内标签中?

移动端的自动适配不会将行内px转为rem,写在css中可以实现转化。

name:该属性是svg的名字。

color:该属性是svg颜色,传递进去到fill属性中。

20221011134157.bmp

展示一下svg列表

为什么展示一下?

不展示一下,你怎么知道用什么那个svg,最好还是能点击可以复制的(强行自己添加需求)。

vant的插件?

这个项目是移动端,所以引入的有vant插件,去布局排版,也就有了我上面提醒的font-size问题。

<script setup>
import { Col as vanCol, Row as vanRow } from "vant";
import { clickCopy } from "./index.js";
import { ref, computed } from "vue";
​
const requireContextSvg = import.meta.globEager("../../../assets/svgs/*.svg");
const svgIconList = Object.keys(requireContextSvg).map(fileName => {
  fileName = fileName.replace(/[^]+[/]/, "").replace(/.svg/g, "");
  return fileName;
});
const svgIconListCopyToForList = computed(() => {
  let newList = [];
  let vantRowSize = svgIconList.length / 4;
  for (let index = 0; index < vantRowSize; index++) {
    newList.push(svgIconList.slice(4 * index, 4 * (index + 1)));
  }
  return newList;
});
</script><template>
    <vanRow v-for="(itemRow,indexRow) in svgIconListCopyToForList" align="center" class="svg-van-row" :key="indexRow">
        <vanCol class="svg-van-col" span="6" v-for="(itemCol, indexCol) in itemRow" :key="indexCol">
            <div class="svg-van-icon" @click="clickCopy(`<svg-icon name='${itemCol}'/>`)">
                <svg-icon class="icon" :name="itemCol" color="#7bd57b"/>
                <p class="name">{{itemCol}}</p>
            </div>
        </vanCol>
    </vanRow>
</template><style lang="scss" scoped>
  .icon-container{
    .svg-van-row{
      // text-align: center;
      .svg-van-col{
        padding: 15px;
        .svg-van-icon{
          display: flex;
          flex-direction: column;
          align-items: center;
        }
      }
    }
    .icon{
      font-size: 46px;
    }
    .name{
      font-size: 14px;
      margin: 8px 0 0 0;
      height: 28px;
      text-align: center;
    }
  }
</style>

解析:

import.meta.globEager:vite提供的批量导入某些文件,实现项目的模块化的函数。

来看一下import.meta - JavaScript | MDN (mozilla.org),里面写了用法。

import.meta.globEager("../../../assets/svgs/*.svg")中*代表一个目录下的文件,**就表示是一个目录下的文件与文件夹(可以拿到该文件夹下面全部的该类型的文件)。

svgIconListCopyToForList:这个就是自己对数组的重新构建,下面template需要的循环格式。

复制代码

下面是复制代码的代码段,不适合部分手机端使用,是会出现兼容问题的,浏览器上开发使用时绰绰有余。 注意:如果想在手机端H5页面使用复制,请去关注一下其他方法,该方法可能不太够用。

import { Toast } from "vant";
// 能简单复制,不设计兼容
export function clickCopy(htmlValue) {
  let isCopySuccess = true;
  if (!htmlValue) isCopySuccess = false;
  let copyText = isCopySuccess ? "复制成功:" : "复制失败:";
  if (navigator.clipboard && window.isSecureContext) {
    Toast(copyText + htmlValue);
    return navigator.clipboard.writeText(htmlValue);
  } else {
    const textArea = document.createElement("textarea");
    textArea.value = htmlValue;
    // 使text area不在vieport中,同时不可见
    document.body.appendChild(textArea);
    textArea.focus();
    textArea.select();
    Toast(copyText + htmlValue);
    document.execCommand("copy");
    textArea.remove();
    return true;
  }
};

1.gif

总结

1、设计师发送png图片,你可以上传到阿里适量库中,再选择svg复制下来,不就是纯洁的svg代码了。

2、svg封成组件自己团队用还是很爽的。

3、祝你我好运