使用vue-svg-loader加载svg文件并自定义loader实现通过css改变svg颜色

548 阅读1分钟

github地址:github.com/visualfanat…

在webpack、Vue CLI、Nuxt中使用都有介绍,可以去看文档,在这里就不做展开了。

Vue CLI 中使用去掉 svg 颜色的自定义loader svgo-remove-single-fill.js

// 使用vue-svg-loader加载图标,用于<x-icon>扩展图标
  const svgPath = resolve('src/assets/svg');
  config.module
    .rule('svg')
    .exclude.add(svgPath)
    .end();
  config.module
    .rule('icons')
    .test(/\.svg$/)
    .include.add(svgPath)
    .end()
    .use('babel-loader')
    .loader('babel-loader')
    .end()
    .use('vue-svg-loader')
    .loader('vue-svg-loader')
    .options({
      svgo: {
        plugins: [{
          // 去除单色图标的fill,使svg可通过css修改颜色
          svgoRemoveSingleFill: require('./loaders/svgo-remove-single-fill'),
        }],
      },
    })
    .end();

svgo-remove-single-fill.js 的实现如下:

/**
 * svgo插件,去掉单色svg的fill,使svg支持用css修改颜色
 */

const ignoreEl = ['defs']; // 需要忽略的节点

function fn(data) {
  // 1、获取所有节点最终解析的fill和stroke值
  const finals = getFinalFillStroke(data);
  const allStroke = new Set(// 所有有效stroke值
    finals.filter(item => item.stroke && item.stroke !== 'none' && parseInt(item.strokeWidth) > 0).map(item => item.stroke),
  );
  const allFill = new Set( // 所有有效fill值
    finals.filter(item => item.fill && item.fill !== 'none').map(item => item.fill),
  );
    // 2、判断有效的stroke与fill是否唯一且相等
  if (allStroke.size <= 1 && allFill.size <= 1) {
    const stroke = allStroke.values().next().value,
      fill = allFill.values().next().value;
    if ((!stroke || isSimpleColor(stroke)) &&
      (!fill || isSimpleColor(fill)) &&
      (!stroke || !fill || stroke === fill)) {
      // 3、去掉非none的fill
      setFinalFill(data);
    }
  }
  return data;
};

// 获取节点(及后代节点)最终解析的fill和stroke值
function getFinalFillStroke(el, parentFill, parentStroke, parentStrokeWidth = 1) {
  const fill = getFill(el, parentFill);
  const { stroke, strokeWidth } = getStroke(el, parentStroke, parentStrokeWidth);
  if (el.content) { // 存在子节点
    return el.content.reduce((sum, child) => {
      if (ignoreEl.includes(child.elem)) {
        return sum;
      } else {
        return sum.concat(getFinalFillStroke(child, fill, stroke, strokeWidth));
      }
    }, []);
  } else {
    return {
      fill,
      stroke,
      strokeWidth,
    };
  }
}
// 在判断为单色图标后,设置节点(及后代节点)的fill
function setFinalFill(el, parentFill) {
  if (ignoreEl.includes(el.elem)) return;
  const fill = getFill(el);
  if (el.content) { // 存在子节点
    el.content.forEach(child => {
      setFinalFill(child, fill || parentFill);
    });
    // 存在子节点,则去掉自身的fill
    if (el.attrs) {
      delete el.attrs.fill;
    }
  } else {
    if (fill && fill !== 'none') {
      if (el.attrs) delete el.attrs.fill; // 去掉有效的fill
    } else if (!fill && parentFill === 'none') {
      setDeep(el.attrs, 'fill', { // 最终解析fill为none
        local: 'fill',
        name: 'fill',
        prefix: '',
        value: 'none',
      });
    }
  }
}
// 获取当前节点的有效fill值
function getFill(el, parentFill) {
  return getDeep(el.attrs, 'fill', 'value') || parentFill;
}
// 获取当前节点的有效stroke和strokeWidth
function getStroke(el, parentStroke, parentStrokeWidth) {
  return {
    stroke: getDeep(el.attrs, 'stroke', 'value') || parentStroke,
    strokeWidth: getDeep(el.attrs, 'stroke-width', 'value') || parentStrokeWidth || 1,
  };
}
// 判断颜色是否为简单颜色
function isSimpleColor(c) {
  return !c || /^(#[\da-f]+|[a-z]+)$/i.test(c);
}
// 深度获取对象的属性值
function getDeep(obj, ...keys) {
  let v = obj;
  if (!v) return v;
  for (let k of keys) {
    v = v[k];
    if (v === null || v === undefined) return;
  }
  return v;
}
// 深度设置对象的属性值
function setDeep(obj, ...keys) {
  for (let i = 0, k; i < keys.length - 2; i++) {
    k = keys[i];
    if (!obj[k]) {
      obj[k] = {};
    };
    obj = obj[k];
  }
  obj[keys[keys.length - 2]] = keys[keys.length - 1];
}

module.exports = {
  name: 'removeSvgSingleFill',
  type: 'full',
  description: 'remove svg single fill',
  fn,
};

目前不支持多颜色复杂svg的颜色处理