写点什么

SVGIcon 组件的构建与使用

  • 2022 年 4 月 12 日
  • 本文字数:6704 字

    阅读完需:约 22 分钟

SVGIcon 组件的构建与使用

SVGIcon 是一个可以在项目任意地方使用的「图标组件」,组件使用者只需指定图标名称、颜色等属性,HTML 页面即可渲染出对应的 SVG 图标。本文将带大家了解 SVGlcon 组件的构建与使用。

背景

SVG 是什么?

SVG(Scalable Vector Graphics)可缩放矢量图形,是一种用于描述基于二维的矢量图形的 XML 标记语言,其基本矢量显示对象包括矩形、圆、椭圆、多边形、直线、任意曲线等,还能显示文字对象和嵌入式外部图像(包括 PNG、JPEG、SVG 等)。实际项目中大多数图标都是使用的 SVG 图标文件,其主要有以下几个优点

  • 内容可读,文件是纯粹的 XML。

  • 图像文件小,可伸缩性强。

  • 矢量放缩,能以不牺牲图像质量为前提,进行任意缩放。

  • 还能基于 DOM 模型实现动态和一些交互功能。

如何将 SVG 效果应用于 HTML 内容中?

在 HTML 文档写入类似于如下内容,则能在页面中渲染出对应的图标。

<div>  <svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24">    <path d="M0 0h24v24H0V0z" fill="none"/>    <path d="M18 20H4V6h9V4H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-9h-2v9zm-7.79-3.17l-1.96-2.36L5.5 18h11l-3.54-4.71zM20 4V1h-2v3h-3c.01.01 0 2 0 2h3v2.99c.01.01 2 0 2 0V6h3V4h-3z"/>  </svg></div>
复制代码


图标一


概括来讲,SVG 文件内容写入 HTML 文档中即可将 SVG 效果渲染展示到页面中。

如何在项目中优雅地使用大量的 SVG 图片文件?

前端实践项目时,为了追求良好的用户体验,难免会使用到大量的符合项目特色的图标,来丰富美化页面内容。所以随着项目逐步完善,需要使用到的图标文件肯定也会随之增加,如何优雅地在项目中使用大量的 SVG 图片文件?这是我们目前需要考虑以及解决的重点问题之一。


实际项目开发中,采用直接将 SVG 文件内容(实质的 XML 内容)写入到 HTML 文档对应位置去渲染我们所预期的图标图形这种方式,可行但不可取!因为这种方式要求使用者在项目每个页面中每个需要展示 SVG 图标的位置,都要将图标文件内容完整的写入对应的 DOM 中,而且 SVG 内容较繁杂,直接写入 DOM 非常影响我们代码的美观和可阅读性。总之这样的操作太过笨重,严重缺乏灵活性和可扩展性


经过技术调研,我们发现 sprite-svg 结合 use 元素的使用方式,可以很好的解决这个问题。

  • 将项目中各个图标合并成一个包含多个 symbol 的 SVG 文件。

  • 在需要使用图标的地方,通过 use 元素引用对应的 symbol。


其工作原理是:use 元素从 SVG 文档内取得目标节点,并且复制它的内容。效果等同于目标节点被克隆到一个不可见的 DOM 中,然后将其粘贴到 use 元素的位置上。


Demo:

<!-- sprite.svg 文件目录:/dist/images/sprite.svg --><svg width="0" height="0" class="hidden">  <symbol xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="add">    <path fill="none" d="M0 0h24v24H0V0z"></path>    <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path>  </symbol>  <symbol xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" id="add_photo">    <path d="M0 0h24v24H0V0z" fill="none"/>    <path d="M18 20H4V6h9V4H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-9h-2v9zm-7.79-3.17l-1.96-2.36L5.5 18h11l-3.54-4.71zM20 4V1h-2v3h-3c.01.01 0 2 0 2h3v2.99c.01.01 2 0 2 0V6h3V4h-3z"/>  </symbol></svg>
复制代码


<div>  <svg>    <use xlinkHref={`/dist/images/sprite.svg#add_photo`} />  </svg></div>
复制代码


图标二


当然,如果 sprite-svg 已经注入到了 HTML 文档中,use 元素的 xlinkHref 则可省去路径,直接使用 #id 去获取目标节点。

SVGIcon 图标组件的构建

有了以上知识储备,我们可以大致给出 SVGIcon 组件的构建方向:

  • 基于 use 元素,封装一个通用灵活的图标组件。

  • 聚合 SVG 图标,生成 sprite-svg 文件。

[任务一] 封装图标组件

组件 Props 的定义

首先需要定义好这个组件的 interface 输入,我们所期望的通用 SVG 组件,主要需要实现以下几点: 指定图标 name、color 等属性,渲染对应的带有指定颜色的 SVG 图标。 指定图标的 size,渲染对应大小的 SVG。


我们将组件的 Props 输入定义如下,在 React.SVGProps 的基础上扩展 name、size、color 属性。

export interface Props extends React.SVGProps<SVGSVGElement> {  name: string;  size?: number;  color?: iconColor;}
复制代码

组件内部实现思路

组件内部的实现,需要考虑以下几点: 

  • 如何根据 name 去 SVG 文档中获取目标节点。

  • 如何根据 size 指定 SVG 的大小。

  • 如何根据 color 去指定 SVG 的颜色。


解决思路: 

  • 使用 use 元素去 sprite.svg 文档中根据 name 获取目标节点。

  • SVG 图标的 size 以及 color 等样式属性,可以使用 css 或者 style 控制。

难点:

难点一:如果使用者指定了组件的 size,同时又有通过 class 或者 style 去指定 SVG 的大小,组件内部到底以哪一个为准?我们以 size 为准,如何实现呢?

  • style 行内样式的优先级比 class 指向的样式高(class 意旨内嵌样式表或者外嵌样式表中的某个样式)。

  • style 样式的规则之一是,在没有使用 !important 的前提条件下,有重复声明的样式时,以最后一次声明的为主。


所以我们组件内部使用以下方式解决了这个问题:

// 伪代码const size = 56;const _style: React.CSSProperties = {  ...style,  width: `${size}px`,  height: `${size}px`,};
<svg style={_style}> <path fill="none" d="M0 0h24v24H0V0z"></path> <path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"></path></svg>
复制代码

经过以上的代码操作,SVG 则是一个根据 size = 56 得到的一个大小为 56px 的图标啦!

图标三


说明:

关于 css 优先级的学习,参考: 优先级 — css (层叠样式表) | MDN


难点二: 组件如何实现自动继承父级 color,并且做到兼容单色 icon 以及双色 icon 两种图标呢?

先给大家简单介绍一下 SVG 着色原理:

  • SVG 中 fill 可以直接使用 css 指定其颜色。

  • SVG 的 color 属性,可以为其 fill、stroke 属性提供一个潜在的间接值 currentColor,具体是什么意思呢?比如我指定 SVG 的 color=red,那么 SVG 中所有 path 上 fill=currentColor 的地方都会着色为 red。(参看下方 Demo)

  • 根据 fill='currentColor' 的特质,验证推断出 SVG 的 color 属性若为 currentColor,则能直接继承父级的颜色。


Demo:

<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" version="1.1">  <g color="red">    <rect width="50" height="50" fill="currentColor" />    <circle r="25" cx="70" cy="70" stroke="currentColor" fill="none" stroke-width="5" />  </g></svg>
复制代码


图标四


基于以上三点,我们决定采用双色 icon 向下兼容单色 icon 的处理方式来实现组件,大致整理出这个问题的解决思路如下:

1、图标组件默认为单色 icon,我们可以给组件一个默认的 svg-icon 的 class 去指定 color 以及 fill 两个值都为 currentColor。

.svg-icon {  color: currentColor;  fill: currentColor;}
复制代码


2、组件接受 color 参数,指定为双色 icon。同样的道理通过 css 样式去指定 SVG 的 color 和 fill 属性,因为是双色 icon,所以 color 和 fill 属性的颜色值需要有一个固定的差值,假设我们拿到的 color=red,我们则可以写如下的 css 实现主色为红色的双色 icon。

.svg-icon--red {  color: var(--red-600);  fill: var(--red-400);}
复制代码

需要注意的是,我们需要限定 color 的取值范围,支持有限集的 color 双色。这样则可以在 css 文件中,将所有的双色 icon 样式定制,组件侧引入后,内部就可以只根据 color 去添加对应的颜色样式实现双色了。


Demo:

// 伪代码 svg-icon 以及 svg-icon-${color}这些样式都需要提前在css文件中定义好<svg  className={classnames('svg-icon',  {  [`svg-icon--${color}`]: color,  })}>  <use xlinkHref={`/dist/images/sprite.svg#${name}`} /></svg>
复制代码

有了以上的构建思路,我们就可以落实图标组件了!

源码

SVG 组件的实现源码:

type iconColor = 'red' | 'blue' | 'primary';  //可以根据需要自行添加
function Icon( { name, size = 16, color, changeable, disabled, clickable, className, style, ...props }: Props, ref?: ForwardedRef<SVGSVGElement>,): JSX.Element { const _style: React.CSSProperties = { ...style, width: `${size}px`, height: `${size}px`, };
return ( <svg {...props} ref={ref} data-name={name} style={_style} className={cs('svg-icon', className, { 'svg-icon--changeable': changeable, 'svg-icon--clickable': clickable, 'svg-icon--disabled': disabled, [`svg-icon--${color}`]: color, })} > <use xlinkHref={`/dist/images/sprite.svg#${name}`} /> </svg> );}
复制代码


SCSS 源码:

.svg-icon {  @apply align-middle;  display: inline-block;  color: currentColor;  fill: currentColor;}
.svg-icon--blue { color: var(--blue-600); fill: var(--blue-400);}
.svg-icon--red { color: var(--red-600); fill: var(--red-400);}
.svg-icon--primary { color: var(--primary-600); fill: var(--primary-400);}
.svg-icon--disabled { opacity: 0.5; cursor: not-allowed;}
.svg-icon--changeable { &:hover, &:active { color: var(--icon-coloured-color); fill: var(--icon-coloured-fill); }}
.svg-icon--clickable { cursor: pointer;}
复制代码

[任务二] 聚合 SVG 图标

目前,SVG Sprite 最佳实践是使用 symbol 元素。 将各个图标合并成一个包含多个 symbol 的 SVG 文件,在需要使用图标的地方,引用对应的 symbol 即可。那么如何将多个图标合并成一个包含多个 symbol 的 SVG 文件呢?


  • 手动合并,这是最简单易上手的方式。项目初期使用少量 icon 的情况可以使用。但是当项目逐渐庞大起来,一次性需要添加使用特别多 icon 的时候,手动合并明显不合时宜。

  • 利用插件合并,这无疑是最佳选择,合并 SVG 的插件特别多,大家可以自行选择喜欢的插件来进行合并。


全象云低代码平台使用的是 svg-spreact 插件来将 SVG 图标文件合并压缩,并进行转换为包含 symbol 的 SVG 文件,操作流程如下:


1、在每次项目构建的时候,执行 JavaScript 脚本,遍历存放 SVG 的文件夹下所有 SVG 图标文件内容,输出获得 [{file: 文件名, cont:文件内容}] 这样的一维数组。

2、根据插件合并方法参数的需要,将遍历获得的数组信息进行处理并且赋值给对应的 svgSpreact 方法。

// 核心代码const svgSpreact = require('svg-spreact');const { defs } = await svgSpreact(input, { tidy: true, processId: iconID, svgoConfig })
复制代码


参数说明:

  • input:SVG 图片文件内容数组。

  • iconID:合并时的 SVG 的 id,建议使用 SVG 图标文件的名称作为 id。

  • svgoConfig:压缩 SVG 内容的配置。


// svgoConfig 配置代码示例module.exports = {  multipass: false,  plugins: [    {      name: 'removeAttributesBySelector',      params: {        selectors: [          { selector: "[fill = 'none']", attributes: 'fill' },          { selector: "[fill = '#94A3B8']", attributes: 'fill' },        ],      }    },    {      name:  'removeTitle',      active: true    }  ];
复制代码


3、经过上一步操作执行 svgSpreact 方法后,得到的返回结果对象中的 defs 即为合并后包含多个 symbol 的 SVG 文件内容。最后将返回的结果内容输出写入到 /dist/images/sprite.svg 中:

const fs = require('fs');const pathName = `/dist/images/sprite.svg`;const spriteFile = path.join(basePath, pathName);fs.writeFileSync(spriteFile, defs);
复制代码


至此 SVG 图标既合并完成,合并后的 sprite-svg 文件的路径为 /dist/images/sprite.svg。


注意:

实际项目中,大多都不能直接将聚合获取得到的 SVG 文件内容直接输出写入到 /dist/images/sprite.svg 中。在我们的项目场景中,直接输出会导致所有的 icon 都保留原有的颜色,不能实现我们预期需要的单色、双色图标。怎么解决这个问题呢?


单色、双色 icon 的原理以及实现思路有在本文「难点」部分讲解,结合这个知识点,我们可以对其进行以下处理:


首先确定 SVG 图标中哪一个 path 需要设置为主色,哪一个 path 需要设置为辅色。全象云前端是将 SVG 图标中的 #475569 作为主色,#94A3B8 作为辅色的。

// 示例 SVG 图标<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">  <path fill-rule="evenodd" clip-rule="evenodd" d="M10 18.3333C7.68089 18.3333 5.55749 17.8478 4.16667 16.9278V11.1428C5.55749 12.0628 7.68089 12.5483 10 12.5483C12.3191 12.5483 14.4425 12.0628 15.8333 11.1428V16.9278C14.4425 17.8478 12.3191 18.3333 10 18.3333Z" fill="#94A3B8"/>  <path fill-rule="evenodd" clip-rule="evenodd" d="M4.1667 3.57971C4.17712 4.62984 6.78479 5.48008 10 5.48008C13.2152 5.48008 15.8229 4.62984 15.8333 3.57971L15.8333 9.64309C14.4425 10.5631 12.3191 11.0486 10 11.0486C7.68089 11.0486 5.55749 10.5631 4.16667 9.64309V3.57971H4.1667Z" fill="#94A3B8"/>  <ellipse cx="10" cy="3.57335" rx="5.83333" ry="1.90668" fill="#475569"/></svg>
复制代码


图标五


聚合时,利用 svgoConfig 将内容中辅色值为 #94A3B8 的去掉,这样能 fill 使用 color 作为默认间接颜色值实现单色,可以单独制定颜色,实现双色。

module.exports = {  multipass: false,  plugins: [    {      name: 'removeAttributesBySelector',      params: {        selectors: [          { selector: "[fill = 'none']", attributes: 'fill' },          { selector: "[fill = '#94A3B8']", attributes: 'fill' },        ],      }    },    {      name:  'removeTitle',      active: true    }  ];
复制代码


在 svgSpreact 聚合后拿到的 defs 内容,先将其中主色部分 #475569 字符串替换为 currentColor,然后再写入 /dist/images/sprite.svg。

// 最终合并得到的 sprite.svg 文件<svg width="0" height="0" class="hidden">  <symbol xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" id="database">    <path fill-rule="evenodd" d="M10 18.333c-2.32 0-4.443-.485-5.833-1.405v-5.785c1.39.92 3.514 1.405 5.833 1.405 2.32 0 4.443-.485 5.833-1.405v5.785c-1.39.92-3.514 1.405-5.833 1.405zM4.167 3.58c.01 1.05 2.618 1.9 5.833 1.9 3.215 0 5.823-.85 5.833-1.9v6.063c-1.39.92-3.514 1.406-5.833 1.406-2.32 0-4.443-.486-5.833-1.406V3.58z" clip-rule="evenodd"></path>    <ellipse cx="10" cy="3.573" fill="currentColor" rx="5.833" ry="1.907"></ellipse>  </symbol></svg>
复制代码

图标组件的使用

完成了图标组件的封装,以及 sprite.svg 的聚合,接下来我们就可以来灵活使用我们的 icon 图标组件了。这里有个前提是,要使用到的图标必须都已经聚合到了 sprite.svg 中,使用才能生效。

前期准备:

在需要使用图标组件的 jsx 文件里,先将封装好的 icon 组件引入进来,后续才能使用。

import Icon from '/component/icon' // 组件路径视具体项目而定
复制代码

使用方式

  • name:指定图标内容。

  • size:指定图标的大小。

  • color:指定图标为双色图标,并且颜色为 color 的值。若不传 color 则默认图标为单色图标,颜色默认从父级继承。

  • style:自定义图标组件的行内样式。

  • className:自定义图标样式。


示例 -- 单色图标

<span style={{ color: 'blue' }}>  <Icon size={20} name='gateway' /></span>
复制代码


图标六


示例 -- 双色图标

<div>  <Icon color="green" size={24} name='gateway' /></div>
复制代码


图标七


当然使用者也可以去使用 css 控制 SVG 组件图标的颜色。


Demo:

<div>  <Icon        name='datatbase'        size={24}        style={{color: '#e11d48'; fill: '#8CADFF'}}    /></div>
复制代码


图标八

总结

全象云低代码平台的图标组件的特点是,同时支持单色、双色 icon。使用方便,只需定义 color 属性,就可以控制图标的颜色模式是单色还是双色,以及控制颜色值。


公众号:全象云低代码

GitHub:https://github.com/quanxiang-cloud/quanxiang

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2021.08.23 加入

一个持续分享硬核技术(包含前端、后端)的团队,欢迎关注呀! 公众号:全象云低代码 GitHub:https://github.com/quanxiang-cloud/quanxiang

评论

发布
暂无评论
SVGIcon 组件的构建与使用_前端_全象云低代码_InfoQ写作平台