任意svg图的变色方法选择

379 阅读2分钟

字体图标大家都已经用过了,在其外层定义一个颜色,图标就能跟随变色,这是因为图标中的svg元素上都未定义fillstroke。但对于一些用户上传的图标不总是如此(比如一些颜色丰富的设备图),我们还想对其进行变色的话有以下两种方法。

fecolor3.png

一、剔除fill和stroke后变色

最先想到的就是剔除各svg元素中的fill,stroke属性,这样在外层元素上使用fill, stroke时内部svg元素就能继承它们,达到整体变色的效果。

以下是将svg字符串添加到一个临时元素,利用DOM选择器查找,来剔除属性的方法。(要注意内部有嵌套的svg,和使用了css外联样式控制的情况)

function removeColorAttribute(svg) {
  const dom = document.createElement('div');
  // 要检测的svg元素
  const svgEls = ['svg','g','path', 'line', 'rect', 'circle', 'use', 'ellipse', 'polygon', 'polyline'];

  dom.innerHTML = svg;
  dom.style.display = 'none';
  document.body.appendChild(dom);

  svgEls.forEach(item => {
    const els = dom.querySelectorAll(item);
    els.forEach(el => {
      let fill = el.getAttribute('fill') || el.style.fill;
      let stroke = el.getAttribute('stroke') || el.style.stroke;
      // 有设置 fill,stroke 属性时移除
      if (fill && fill !== 'none') {
        el.removeAttribute('fill');
        el.style.fill = '';
      }
      if (stroke && stroke !== 'none') {
        el.removeAttribute('stroke');
        el.style.stroke = '';
      }
      // 避免使用了css外联或内嵌样式控制
      el.style.fill = 'unset';
      el.style.stroke = 'unset';
    });
  });

  const svgEl = dom.querySelector('svg');

  return new XMLSerializer().serializeToString(svgEl);
}

使用示例

<div id="el" style="fill:yellow;stroke:#3d80e4;"></div>
<script>
const svgStr = removeColorAttribute(`<svg><rect fill="#000"... </svg>`);
document.querySelector('#el').innerHTML = svgStr;
</scipt>

这种方式对于颜色单一的图标几乎都能满足,但若是对那种写实风格,有多种颜色的图标会破坏它们的风格。svg中有图片的情况,这种方法也会无效。

二、使用滤镜变色

使用svg的feColorMatrix滤镜是一种更简单的方式,我们只要提前定义好各种变色的滤镜即可。

<defs>
<!--红色过滤器-->
<filter id="filter-red_09jn234v">
    <feColorMatrix type="matrix" values="1 1 1 0 0
                                          0.1 0.1 0.1 0 0
                                          0.1 0.1 0.1 0 0
                                          0 0 0 1 0" >
</filter>
<!--绿色过滤器-->
<filter id="filter-green_9i34abs">
    <feColorMatrix type="matrix" values="0.4 0.4 0.4 0 0
                                          0.76 0.76 0.76 1 0
                                          0.2 0.2 0.2 0 0
                                          0 0 0 1 0" >
</filter>
</defs>
<script>
const colorFilterMap = Object.freeze({
  'red': 'filter-red_09jn234v',
  'green': 'filter-green_9i34abs',
  ...
});
// svg: svg的字符串
function addColorFilter(svg,mode){
  const div = document.createElement('div');
  const item = colorFilterMap[mode];

  div.innerHTML = svg;
  // 添加滤镜
  item && div.querySelector('svg').setAttribute('filter',`url(#${item})`);
  
  return div.querySelector('svg').outerHTML;
}
</script>

上面两个红色、绿色的滤镜在多数情况下表现还可以,但一遇到黑色则效果较差,因为黑色的r,g,b值都比较接近0,与上面矩阵相乘后各通道色值对比度变小,遇到纯黑(rgb(0,0,0))则完全失效

可以将部分色值放到第5列,它在计算中是与色值相加的关系,能保证遇到黑色不会完全失效。

<!--绿色:rgb(66,255,33)-->
<feColorMatrix type="matrix" values="0.26 0 0 0 0
                                        0 1 0 0 0.2
                                        0 0 0.13 0 0
                                        0 0 0 1 0" >

以下是一个通用的feColorMatrix元素的生成方法:

// rgb: [r,g,b] 色值
function createFeColorMatrix(rgb){
      const f2 = (n)=>n.toFixed(2);

      const sum = (rgb[0] + rgb[1] + rgb[2])*1.8;
      // r,g,b3个通道的第5个分量都添加了一个按权重得到的色值。
      const rw = `${f2(rgb[0]/255)} 0 0 0 ${f2(rgb[0]/sum)}`;
      const gw = `0 ${f2(rgb[1]/255)} 0 0 ${f2(rgb[1]/sum)}`;
      const bw = `0 0 ${f2(rgb[2]/255)} 0 ${f2(rgb[2]/sum)}`;

      return `<feColorMatrix type="matrix" values="${rw}\n${gw}\n${bw}\n0 0 0 1 0"/>`;
}

传入你想要的颜色,将得到的结果复制放到<filter>中使用即可,对于多种颜色表现都不差。

变色前后效果如下:

fecolor1.png

fecolor2.png

三、总结

方法一对于多数图标都有效,但因为是所有元素统一变色,会破坏写实风格的图。

改进后的方法二对所有图标变色都有效,不会破坏图标原有风格,使用也更简单,更推荐使用。但因为要保证不破坏图标风格,所以对各图标过滤得到的效果不一,也会与我们想要的过滤色有偏差。

方法二是对整个图标变色的,如果你只想对部分位置变色的话需要使用方法一。