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

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时,

使用svg-sprite-loader之后的HTML

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

- 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)
    }
  }

猜你喜欢

转载自blog.csdn.net/Charonmomo/article/details/129788881