likes
comments
collection
share

探索前端项目中icon使用的最佳实践

作者站长头像
站长
· 阅读数 4

探索前端项目中icon使用的最佳实践

1、前言

我们在网页开发中,无时无刻不在与 icon 打交道, 那么怎么样的实现方式才是最优的呢? 之前在开发中本着“省事”的目的,一直将就,开发过程中每次遇到icon的问题都比较头疼, 尤其是在icon频繁导出,代码重复编写上,着实是一件难忍的事。 实际上“省事”并不“省事”, 相反非常头痛, 最近进行项目重构,于是着手将代码中icon的使用进行梳理, 重新进行替换

2、问题与诉求

本人在工作多年,一直使用icon的途中, 遇到过很多问题, 记录如下

  • 1、 icon 用图片png 还是 svg
  • 2、 图片用网络加载还是本地加载,然后打包到项目中
  • 3、 我想要体积更小,使用最方便
    • 我想要网络只加载一次
    • 我想打包体积更小
  • 4、 svg 在项目中的使用过程中, 如何优雅的使用
    • 我只想引入一次
    • 需要满足hover支持变色
    • 满足传入颜色、背景色
    • 可以通过一行代码搞定、包括父元素hover 也能让icon变色

3、解释

先简单解释一下上面的问题

1、 对于第一个问题: 图片和svg其实各有优势, svg是 矢量图,有不失真的优势, 是靠线条画出来的, 对于简单的侧重线条类的 icon 建议使用 svg, svg是一段代码,体积非常小, 但是对于颜色丰富、色彩绚丽,可能 图片 尤其是 png 格式的图片更加合适, 实际开发中,我们为了保证导出的图片不失真,通常导出 2倍图, 通过css控制实际宽高低于导出的宽高, 那么导致的问题就是图片体积又非常的大。 那么其实对于公司通用的icon来说,一般都是通过figma或者其他平台直接导出, 对于icon来说,可以和设计师一起商量, 设计出大小一致, 尺寸和常用色一致的 svg icon

2、对于第二个问题,其实也是比较取舍的, icon 通过网络加载,就会占用网络带宽, 如果用本地打包资源就会使打包后的体积增大, 通常会根据具体情况进行综合考虑。一些项目可能选择混合使用,将一些核心的、不经常变更的图片打包到项目中,而将一些可能经常更新或较大的图片通过网络加载

4、 方案

4-1、 react 中 svg icon 的使用 与 问题

import svgr from "vite-plugin-svgr";
//...
plugins: [react(), svgr(), splitVendorChunkPlugin(), visualizer()],
//...

通常我们在react中加载svg 是需要在vite.config 中配置一下vite-plugin-svgr 使用时, 导入svg 像导入组件一样

import { ReactComponent as IconCountry } from "./country.svg";

// use
<IconCountry className="icon"></IconCountry>

我们可以在icon中通过类的样式进行更改icon的大小, 但是对于icon的边框颜色和填充色,通常就不是那么容易了, 我们可能需要写出以下的代码

.icon {
  width: 10px;
  height:10px;
    & > svg {
      width: 40px;
      height: 40px;
    }
    & > rect {
      fill: #5051ff;
    }
    & > path {
      stroke: #fff;
    }
  }

像代码写的,如果改svg的颜色,通过样式改通常要将里面的各种路径元素颜色都要更改,非常的不优雅, 这还是没有添加 hover , 添加 hover, 又要对内部 元素全部添加一遍, hover 变色可能还可以通过改样式解决, 那么active, 选中态呢? 增加更多逻辑交互后, 通常我们只能再多导入一个 变色后的icon,才能让代码没有那么恶心

4-2、 使用 react-svg-sprite-generator 解决方案

其实解决上面的问题, 思路就是我们需要通过工具先将所有的svg打包成精灵图, 进行压缩, 去掉本身的边框颜色和fill的颜色, 然后我们可以通过svg <use xlinkHref={{spriteUrl}#{name}} /> 的方式,再项目中找到对应的资源中的icon,并进行设置。 再npm中寻找这个工具的时候恰好找到了这个包, 地址 这个包的使用非常的简单

npm i react-svg-sprite-generator -D
npx svgsprite --src ./icons --dest ./src/icons

可以写到package.json 中 每次 npm run svg 即可

只需要安装后, 在目录下的终端执行一下命令,即可打包出来一个 sprite.svg 的精灵图, 那么使用就很简单了

 <svg
  style={{
    width: size + "px",
    height: size + "px",
  }}
  strokeWidth={strokeWidth}
  stroke={strokeColor}
>
  <use xlinkHref={`${spriteUrl}#${写入想展示的icon的名字大写的)}`} />
</svg>

使用后发现颜色和大小都可以灵活控制了, 而且相比原始的 svg 文件, 打包出来的进行了压缩与处理, 原始的svg文件其实就不再需要了, 当然代码中不使用, 也不会打包到dist目录中, 那么build后的体积其实大幅降低了

那么为了更方便使用, 我们再进一步进行封装, 代码如下

import * as IconNames from "./names";
import spriteUrl from "./sprite.svg";
import { useEffect, useState } from "react";
const SvgIcon = ({
  name,
  color = "#222222",
  hoverColor = "",
  size = 16,
  strokeWidth = 3,
  classNames,
  fill,
}: {
  name: keyof typeof IconNames;
  size?: number;
  color?: string;
  hoverColor?: string;
  strokeWidth?: number;
  classNames?: any;
  fill?: string;
}) => {
  const [strokeColor, setStrokeColor] = useState(color);
  const onMouseHover = () => {
    if (hoverColor) {
      setStrokeColor(hoverColor);
    }
  };
  const onMouseLeave = () => {
    if (hoverColor) {
      setStrokeColor(color);
    }
  };

  useEffect(() => {
    setStrokeColor(color);
  }, [color]);

  return (
    <svg
      style={{
        width: size + "px",
        height: size + "px",
      }}
      strokeWidth={strokeWidth}
      stroke={strokeColor}
      fill={fill || "none"}
      onMouseOver={onMouseHover}
      onMouseLeave={onMouseLeave}
      className={classNames}
    >
      <use xlinkHref={`${spriteUrl}#${name}`} />
    </svg>
  );
};
SvgIcon.IconNames = IconNames;
export default SvgIcon;

使用

<SvgIcon name={IconNames.PRODUCT} color="#f00" hoverColor="#ff0" size={24} />

使用起来就非常简单了, 我们可以方便的控制颜色和尺寸了, 但是我们上面讲过, 能不能实现一行代码就实现样式的 hover, 在组件里 不进行 更新hover状态也能改变颜色呢 , 答案也是可以的, 那么就需要我们使用 tailwind CSS 通过 className的方式传入了

<div className="group bg-red-100 hover:bg-red-300">
        <div className="group-hover:text-[#0f0]">111</div>
        <SvgIcon name={IconNames.PRODUCT} classNames="group-hover:stroke-[yellow]  group-hover:fill-[#f00]" size={24} />
 </div> 

如图: 就是我们通过 tailwindcss 的方式, 实现了hover到父元素,同时控制子元素中的文本和icon变色的例子, 到此,我们就基本实现了我们的需求, 代码也非常的简洁

5、总结

本文通过总结工作中遇到的icon使用的问题, 进行一步步分析拆解, 最终通过使用 npm 中的一个 svg 包,生成了压缩的合并svg的精灵图, 通过svg 的use 使用, 进一步封装成了组件, 然后通过使用 tailwind css 的方式,实现了一行代码即可灵活控制icon的hover变色、更改大小、边框等等需求, 在减少代码build体积、提高开发速度等方面成效显著, 可以说是一劳永逸

6、后续坑点

探索前端项目中icon使用的最佳实践

此方案再开发环境没有问题, 但是打包到线上,在引用icon的时候发生了如图报错, 几经搜索查询,大概原因如下

探索前端项目中icon使用的最佳实践

大概就是直接加载 svg, 不安全,尤其是文件放在 cdn 正好我们就是这种情况, 而且我们项目中起始 svg 的处理也是不直接引用,而是通过loader转化成dom插入页面中的, 比如 vite-plugin-svgr 插件

那么就没有办法解决了吗, 其实改一下 svg 的插入方式就行了, 可以参考一下平头哥的插入svg的两种姿势

链接

我们将 sprite.svg 这个文件不以地址的方式引入,改为通过组件方式引入,然后隐藏掉

    import { ReactComponent as SpriteUrl } from "./sprite.svg";
    <SpriteUrl className="invisible" />

探索前端项目中icon使用的最佳实践

到此,这个bug 就完美解决了