svg颜色部分的处理
我们期望的用法是这样的:
<Icon name="account" color="red" size={[50,50]}/>
或者 size={50} 这样的写法
所以需要三个参数:
- 名称(name)
- 颜色(color)
- 尺寸(size)
经过上一篇的步骤,可以看到name这个需求已经实现了。
size的实现比较简单,就不做太多赘述了(后面的代码中也体现了处理size的过程),直接在svg上添加属性就可以了,举个简单的例子:
return (
<svg
width={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时,
我项目中的目录结构是这样的:
- 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}`).children
for(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;
不传入color的效果:
<div>
<p>test2</p>
<Icon name="account" size="200"/>
<Icon name="pwd"/>
</div>
适配
之前我用来测试的svg图基本都是从iconfont上下载的,结构比较统一,比如account.svg这个图片的结构中只有 path 这一种标签:
但是项目中有时候设计师提供的一些svg图片层级和标签比较多(比如有rect、g等多种标签),颜色属性不确定在哪个标签的 fill 里,像上面那样很有可能没法更改svg图片的颜色,所以最好遍历每一个标签,于是又做了以下处理保证每一层都遍历到:
const setChildColor = (elem) => {
const {children} = elem
if(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)
}
}