> 文章列表 > react如何设置可自定义颜色的svg图标库(二)

react如何设置可自定义颜色的svg图标库(二)

react如何设置可自定义颜色的svg图标库(二)

svg颜色部分的处理

我们期望的用法是这样的:

<Icon name="account" color="red" size={[50,50]}/>
或者 size={50} 这样的写法

所以需要三个参数:

  • 名称(name)
  • 颜色(color)
  • 尺寸(size)

经过上一篇的步骤,可以看到name这个需求已经实现了。

size的实现比较简单,就不做太多赘述了(后面的代码中也体现了处理size的过程),直接在svg上添加属性就可以了,举个简单的例子:

return (<svgwidth={200}  // 把这里换成变量就可以了,或者将样式统一放在一个对象里,然后用展开符也行height={200}aria-hidden="true"><use xlinkHref={iconName} /></svg>)

关于颜色的处理上,有两种不同的需求:
一是直接去除原来svg自带的颜色,所有的颜色都由用户重新设置(或有一个默认颜色);
二是只有在用户传了颜色这个属性时才使用使用自定义的颜色,否则保留原来svg的颜色;(这个也是我的需求)

针对这两种不同的需求,处理方案是不同的(仅是在使用了svg-sprite-loader的情况下)。第一种需求实现起来比较简单,第二种比较麻烦,下面分别讨论(有些情况我没有实践,所以只给了思路或参考)。

(一)去除原来svg的颜色

有几种思路:
1. 直接去掉原来svg图片中fill的色值;
2. 通过css设置currentColor迂回覆盖原来的颜色;

直接去掉原来fill的色值

可以用一些插件(比如svgo-loader)或者自己写一些脚本处理。

安装svgo-loader

GitHub:svgo-loader

SVGO 将 SVG-as-XML 数据转换为 SVG-as-JS AST 表示形式。然后在所有AST数据项上运行并执行一些操作,最后,SVGO 再将 AST 转换回 SVG-as-XML 数据字符串。
SVGO 是 svg 优化器,包含很多插件。它可以删除和修改SVG元素,折叠内容,移动属性等等。
--------摘自《使用svg-sprite-loader优化Icon》

主要是在配置文件 webpack.config.js 里加入自动消除掉 fill ,具体代码如下(来自掘金作者moonwanger):

{test: /\\.svg$/,use: [{ loader: 'svg-sprite-loader', options: {} },{ loader: 'svgo-loader', options: {plugins:[// 加载时删除svg默认fill填充色{removeAttrs:{attrs: 'fill'}}]}}]},

通过css设置currentColor

只需要新建一个样式文件(比如 icon.less ),写入下面的css;再将这个样式文件引入之前写好的通用组件 icon.js 。在 icon.js 中,为 svg 标签加上 color 属性就可以了。

g[fill] {fill: currentColor;fill-opacity: 1;
}
g[stroke] {stroke: currentColor;stroke-opacity: 1;
}
path[fill] {fill: currentColor;fill-opacity: 1;
}
path[stroke] {stroke: currentColor;stroke-opacity: 1;
}

原理很简单,本质上就是把svg文件中的 g 标签或者 path 标签中控制颜色的属性给改了。

(二)保留原来svg的颜色

有几种思路:
1. 动态引入不同的css文件,类似切换网站不同主题的实现方式;(不过这一种我没有成功)
2. 使用js选择器根据 id 选中对应的标签,当用户传入color属性时给标签增加样式;

第二种方法是我在尝试几个方法失败后才最后采用的,思路很简单:通过分析使用了 svg-sprite-loader 之后的HTML页面可以发现,每一个svg图片对应的symbol id是不一样的;这个symbol标签的孩子就是svg图片的path,其中的 fill 属性就控制了这个标签的颜色。这样我们就可以用 document.getElementById() 选中这个 symbol 标签;之后通过 children 来得到它的子元素,进而控制 path 中的 fill 属性。这样就实现了当用户不传入color时,

react如何设置可自定义颜色的svg图标库(二)

我项目中的目录结构是这样的:

- icons- svg 这个文件夹用来放所有的svg图片
- src- app.js 

Icons.js:

import React, { useMemo, useState, useEffect } from 'react'const Icon = ({name,size,color}) => {console.log(name,size,color)const [svgModule, setSvgModule] = useState();const [svgSize, setSvgSize] = useState({width: 30,height:30});// 允许自定义颜色const setColor = () => {let elem = document.getElementById(`${name}`)if(elem) {let children = document.getElementById(`${name}`).childrenfor(let i=0;i<children.length;i++) { // foreach报错children[i].style = `fill: ${color}`; // 这里不能用with语句,严格模式不支持with}}}// 允许自定义尺寸const setSize = () => {if(!size){setSvgSize({width:30,height:30})return}typeof size === "number" || "string" ? setSvgSize({width:size,height:size}) : (size.length && size.length === 1 ? setSvgSize({width:size[0],height:size[0]}):setSvgSize({width:size[0],height:size[1]}))}// 根据name拿到svg路径const getSvg = async () => {console.log("getSvg")const svg = await import(`../../icons/svg/${name}.svg`)setSvgModule(svg)}const iconName = useMemo(() => {setColor() // 保证页面刷新时不会因为找不到id为name的标签而报错if (svgModule && svgModule.default) {return `#${svgModule.default.id}`}}, [svgModule])useMemo(() => {setSize()}, [size])useMemo( ()=>{setColor()},[color])useEffect(() => {getSvg() }, [])return (<svg{...svgSize}aria-hidden="true"><use xlinkHref={iconName} /></svg>)
}export default Icon

使用时(app.js)传入color的效果:

import React from 'react';
import Icon from "./components/icons"function App() {return (<div><p>test2</p><Icon name="account" size="200" color="pink"/><Icon name="pwd" color="green"/></div>);
}export default App;

react如何设置可自定义颜色的svg图标库(二)
不传入color的效果:

<div><p>test2</p><Icon name="account" size="200"/><Icon name="pwd"/>
</div>

react如何设置可自定义颜色的svg图标库(二)

适配

之前我用来测试的svg图基本都是从iconfont上下载的,结构比较统一,比如account.svg这个图片的结构中只有 path 这一种标签:
react如何设置可自定义颜色的svg图标库(二)

但是项目中有时候设计师提供的一些svg图片层级和标签比较多(比如有rect、g等多种标签),颜色属性不确定在哪个标签的 fill 里,像上面那样很有可能没法更改svg图片的颜色,所以最好遍历每一个标签,于是又做了以下处理保证每一层都遍历到:

   const setChildColor = (elem) => {const {children} = elemif(children) {for(let i=0;i<children.length;i++) {children[i].style = `fill:${color}`if(children[i].children) {setChildColor(children[i])}}}}// 允许自定义颜色const setColor = () => {let elem = document.getElementById(`${name}`)if(elem) {setChildColor(elem)}}