vue3图标终极方案【npm包推荐】vue3-icon-sui(含源码详解)

发布于:2025-09-07 ⋅ 阅读:(16) ⋅ 点赞:(0)

简介

为彻底实现 vue3 项目图标自由,特开发此 npm包 vue3-icon-sui,全品类图标,通通支持!

  • iconify 图标
  • svg 图标
  • font-class 图标

安装

npm i vue3-icon-sui -S

使用

按需导入

任意页面中

import myIcon from "vue3-icon-sui";

myIcon 可为任意自定义的组件名称

全局注册

src/main.ts

import myIcon from "vue3-icon-sui";

const app = createApp(App);
app.component("myIcon", myIcon);

iconify 图标

  • 必要传参 icon – iconify 官网图标的名称,支持翻转 flip
    • 水平翻转 “horizontal”
    • 垂直翻转 “vertical”
    • 水平垂直翻转 “horizontal vertical”
  1. 在 iconify 官网搜索想要的图标
    https://icon-sets.iconify.design/?query=home

在这里插入图片描述
2. 页面中使用
传给属性 icon

<myIcon icon="ic:baseline-home" color="red" size="36" />

在这里插入图片描述

svg 图标(支持多彩)

  • 必要传参 name – 项目的 src/assets/icons/svg 目录中svg 图标的名称
  1. https://www.iconfont.cn/ 中找到喜欢的图标
    在这里插入图片描述
    鼠标悬浮其上时,点击下载

    在这里插入图片描述
    在这里插入图片描述

    可直接下载svg,将其放入项目的 src/assets/icons/svg 目录中,改名为 nice.svg

    也可以复制 svg 代码,新建 src/assets/icons/svg/nice.svg 文件,再将 svg 代码粘贴到 nice.svg 中

  2. 页面中使用
    将 svg 图标的名称传给属性 name

    <myIcon name="nice" size="36" />
    

    效果如下
    在这里插入图片描述
    传入 color 可自定义颜色,但会丧失多彩

    <myIcon name="nice" size="36" color="red" />
    

    在这里插入图片描述

font-class 图标

  1. https://www.iconfont.cn/ 中找到喜欢的图标
    在这里插入图片描述

鼠标悬浮其上时,点击添加入库
在这里插入图片描述

在这里插入图片描述

加入已有项目,或新建项目

在这里插入图片描述
在这里插入图片描述
得到 url

//at.alicdn.com/t/c/font_2261937_vumtsyzbq7d.css

鼠标悬浮在项目的图标上,可一键复制代码,得到 type

icon-nice

在这里插入图片描述

  1. 页面中使用
    必传参数 url 和 type

      <myIcon
        url="//at.alicdn.com/t/c/font_2261937_vumtsyzbq7d.css"
        type="icon-nice"
        size="36"
        color="red"
      />
    

    在这里插入图片描述

属性

属性名 属性值 说明
icon iconify 官网图标的名称,如 “ic:baseline-home” iconify图标必传
flip 水平翻转 “horizontal”
垂直翻转 “vertical”
水平垂直翻转 “horizontal vertical”
仅iconify图标支持
name 项目的 src/assets/icons/svg 目录中svg 图标的名称 svg图标必传
rotate 旋转度数,数值即可,如 90 即顺时针旋转90度 所有图标都支持
color 颜色,如 red 所有图标都支持,但svg的多彩图标会变为纯色
size 大小,数值,如 36 即 36px 所有图标都支持
url font-class图标的css地址,详见使用范例 font-class图标必传
type font-class图标的代码,详见使用范例 font-class图标必传
fontFamily font-class图标的前缀 除非在项目设置中进行了修改,否则使用默认的 icon-font 即可

font-class 图标自定义fontFamily

在这里插入图片描述

在这里插入图片描述
通常不建议修改!

假设修改为 myfont ,则页面使用时,fontFamily属性需传入 myfont

  <myIcon
    url="//at.alicdn.com/t/c/font_2261937_vumtsyzbq7d.css"
    type="icon-nice"
    fontFamily="myfont"
  />

源码

详解见源码注释

<script setup lang="ts">
// 优先推荐【iconify图标】必要传参 icon ,支持翻转 flip
// 搜索图标 https://icon-sets.iconify.design/

// 【svg图标-支持多彩图标】必要传参 name
// 需将svg图标放在 src/assets/icons/svg 目录中,

// 【font图标-不支持多彩图标】必要传参 url 和 type
// 整个项目使用多个图标时,只需有一个图标传入 url 即可

import { computed, onBeforeMount, ref, watch, onMounted } from "vue";
import { Icon } from "@iconify/vue";

// 接收的属性
const props = defineProps({
  icon: {
    type: String,
  },
  // 水平翻转 "horizontal"
  // 垂直翻转 "vertical"
  // 水平垂直翻转 "horizontal vertical"
  flip: {
    type: String,
  },
  name: {
    type: String,
  },
  // 旋转角度
  rotate: {
    type: Number,
  },
  // 图标颜色
  color: {
    type: String,
  },
  // 图标大小
  size: {
    type: [Number, String],
    default: 16,
  },
  url: {
    type: String,
    default: "//at.alicdn.com/t/c/font_2261937_dg35xe8b86.css",
  },
  type: {
    type: String,
  },
  fontFamily: {
    type: String,
    default: "iconfont",
  },
});

onBeforeMount(() => {
  if (props.url) {
    const existingLink = document.querySelector(`link[href="${props.url}"]`);
    if (!existingLink) {
      const link = document.createElement("link");
      link.href = props.url;
      link.rel = "stylesheet";
      document.head.appendChild(link);
    }
  }
});

const className = computed(() => `${props.fontFamily} ${props.type}`);

// 计算样式
const newStyle = computed(() => {
  const style: Record<string, string | number> = {};
  if (props.size) {
    style.width = `${props.size}px`;
    style.height = `${props.size}px`;
  }
  if (props.color) {
    style.color = props.color;
  }

  if (props.rotate) {
    style.transform = `rotate(${props.rotate}deg)`;
  }
  return style;
});

// 状态管理
const svgContainer = ref<HTMLDivElement>();
const loading = ref(true);
const error = ref(null);

// 加载并渲染SVG的函数
const loadAndRenderSvg = async () => {
  try {
    // 重置状态
    loading.value = true;
    error.value = null;

    // 使用动态import导入SVG文件,获取原始内容

    // 加上 ?raw 后,Vite 会直接将 SVG 文件的内容以纯文本字符串的形式返回
    const svgUrl = "/src/assets/icons/svg/" + props.name + ".svg";
    const module = await import(svgUrl + "?raw");
    const svgContent = module.default;

    // 清空容器
    if (svgContainer.value) {
      (svgContainer.value as any).innerHTML = "";
    }

    // 创建临时元素解析SVG内容
    const tempDiv = document.createElement("div");
    tempDiv.innerHTML = svgContent;

    // 获取SVG元素
    const svgElement = tempDiv.querySelector("svg");
    if (!svgElement) {
      throw new Error("导入的文件不是有效的SVG");
    }

    // 设置SVG属性
    svgElement.setAttribute("width", props.size + "px");
    svgElement.setAttribute("height", props.size + "px");

    if (props.color) {
      // 替换SVG颜色
      replaceSvgFillColor(svgElement, props.color);
      // svg 图片本身没有 fill 时,添加fill
      svgElement.setAttribute("fill", props.color);
    }
    // 将SVG元素添加到容器
    if (svgContainer.value) {
      svgContainer.value.appendChild(svgElement);
    }
  } catch (err: any) {
    error.value = err.message || `无法加载图标: ${props.name}`;
  } finally {
    loading.value = false;
  }
};

/**
 * 替换SVG元素的fill颜色值
 * @param svgElement 目标SVG元素
 * @param newColor 新的颜色值(可以是十六进制、rgb、rgba或颜色名称)
 * @returns 是否成功替换颜色
 */
function replaceSvgFillColor(
  svgElement: SVGElement,
  newColor: string
): boolean {
  if (!svgElement) {
    return false;
  }

  try {
    // 查找所有带有fill属性的路径元素
    const pathElements = svgElement.querySelectorAll("path[fill]");

    if (pathElements.length === 0) {
      return false;
    }

    // 替换每个path元素的fill属性
    pathElements.forEach((path) => {
      path.setAttribute("fill", newColor);
    });

    return true;
  } catch (error) {
    return false;
  }
}

// 监听props变化,重新加载图标
watch(
  () => [props.name, props.size, props.color],
  () => {
    loadAndRenderSvg();
  }
);

// 组件挂载时加载图标
onMounted(() => {
  if (props.name) {
    loadAndRenderSvg();
  }
});
</script>

<template>
  <Icon
    v-if="props.icon"
    :icon="props.icon"
    :style="newStyle"
    :flip="props.flip"
  />

  <template v-else-if="props.name">
    <!-- 加载状态 -->
    <div v-if="loading" class="loading">加载中...</div>

    <!-- 错误状态 -->
    <div v-if="error" class="error">图标加载失败: {{ error }}</div>

    <!-- SVG容器 - 动态渲染的SVG将插入到这里 -->
    <div ref="svgContainer" v-else></div>
  </template>

  <i
    v-else="props.type"
    :class="className"
    :style="{
      fontSize: props.size + 'px',
      color: props.color,
      ...newStyle,
    }"
    style="display: inline-block"
  >
  </i>
</template>

网站公告

今日签到

点亮在社区的每一天
去签到