svg之间还能互相影响?

背景

最近搞了一个项目,基于React,用了本身的脚手架fdt。css

svg的使用

项目就不上图了,总之其中存在各式各样的图标。最终选择了svg来控制图标的显示,缘由主要有以下几方面:html

  • 相比传统的图片,尺寸更小,可压缩性更强
  • 可伸缩,更清晰
  • 方便读取和修改
  • 设计软件直接导出

svg的使用方式多种多样,适合本身的才是最好的。下面简单介绍下咱们的项目如何在fdt脚手架中使用了svg。由于fdt基于webpack打包,因此webpack中必不可少须要增长针对svg的配置。代码以下:react

// webpack.config.js

{
    test: /\.svg$/,
    use: [
    {
        loader: path.resolve(__dirname, "./fdtsvgloader")
    },
        "svg-loader"
    ],
    include: [path.resolve(opts.baseDir, "src")],
    exclude: [path.resolve(opts.baseDir, "src/svginline")]
}
复制代码

咱们依然使用svg-loader进行svg的处理,可是svg-loader返回的是一个包含attributescontent的对象,咱们没法直接使用。处理后的结果以下代码所示:webpack

module.exports = {
  attributes: {
    xmlns: 'http://www.w3.org/2000/svg',
    viewBox: '0 0 1024 1024'
  },
  content: '<path d="M441.9 167.3l-19.8-19.8c-4.7-4.7-12.3-4.7-17 0L224 328.2 42.9 147.5c-4.7-4.7-12.3-4.7-17 0L6.1 167.3c-4.7 4.7-4.7 12.3 0 17l209.4 209.4c4.7 4.7 12.3 4.7 17 0l209.4-209.4c4.7-4.7 4.7-12.3 0-17z"/>'
}
复制代码

所以,咱们在fdt中单独写了一个loader来获得咱们想要的svg格式。代码以下:web

// fdtsvgloader.js

module.exports = function(source) {
  return `
    ${source}
    var fdtsvg = require('fdt-svg-loader')
    module.exports = fdtsvg(module.exports)
    `;
};

// fdt-svg-loader

var React = require("react");
module.exports = function(svg) {
  const content = svg.content;
  return function(props) {
    const newprops = { viewBox: "0 0 1024 1024", height: "20px", fill: "#000" };
    newprops.dangerouslySetInnerHTML = { __html: content };
    newprops;
    return React.createElement("svg", { ...newprops, ...props });
  };
};

复制代码

其中,fdtsvgloader.js中接受的参数即为svg-loader处理后的结果。最后,通过fdt-svg-loader处理后,获得了React建立的svg元素,并包含默认属性viewBox,height以及fill值。所以咱们在组件中能够以下方式引用svg:浏览器

// demo.tsx

import PptIcon from "@/image/newppt.svg";

export default class Demo extends Component {
    render() {
        return <PptIcon width="18" height="18" viewBox="0 0 27 34" />;
    }
}   

复制代码

demo中传递的属性即可覆盖默认属性,灵活控制svg的大小。至此,咱们在项目中愉快的使用svg来控制各式各样图标的显示。安全

遇到的问题

效果图
可是忽然有一天,在一个慵懒的午后,咱们的测试同窗忽然告诉我,页面中的图标从左边变成了右边的样子。短暂的惊慌以后,我迅速抄起键盘寻找bug的所在之处。 实不相瞒,在作此项目以前,我较少涉猎svg的知识,对于svg我自己并非很熟悉。所以,寻找bug的过程当中遇到了一些困难和挫折。在较短的时间内并无迅速找到问题所在,所以我首先想到的是从新更换一下图标(设计软件中导出的图标咱们作过手动处理)。我惊奇的发现,这个方法竟然好用,果断完成上线。 过后慢慢琢磨这个事情,一个svg图片并无受到外部CSS的影响,为何会忽然致使问题呢?为了更快的发现问题,我仔细研究了一下咱们的svg图标的结构,学到了一些关于svg的内容:

  • <g>该标签表明组合
  • <defs>定义重用图形
  • <polygon>定义多边形
  • <mask>定义蒙层
  • <use>实现SVG现有图形的重用

既然没法直接找到答案,那只好上排除法来寻找问题所在了。最后发现,问题出现的缘由是咱们新引入的图标影响了原有图标。svg互相影响也真的让我很是震惊。 那究竟是怎么互相影响的呢?缘由就是新的图标中定义了一个mask蒙层,属性id为mask-2。受影响的图标中,path标签的mask属性引用了该mask-2的蒙层,致使新图标的出现影响了部分旧图标。 那么对于直接在html中引入svg,浏览器对于重用图标的寻找机制是怎么样的呢?咱们作了以下测试:bash

<svg width="400" height="300">
        <defs>
            <linearGradient id='white2black'>
                <stop offset="0" stop-color="white"></stop>
                <stop offset="100%" stop-color="black"></stop>
            </linearGradient>
            <mask id="opacity">
                <rect x="0" y="0" width="400" height="300" fill="url(#white2black)"></rect>
            </mask>
        </defs>
        <rect id="back" x="0" y="0" width="400" height="300" fill="#d4fcff"></rect>
        <rect id="front" x="0" y="0" width="400" height="300" fill="#fcd3db" mask="url(#opacity)"></rect>
    </svg>
    <svg width="600" height="300">
        <defs>
            <linearGradient id='white2black'>
                <stop offset="0" stop-color="blue"></stop>
                <stop offset="50%" stop-color="black"></stop>
                <stop offset="100%" stop-color="green"></stop>
            </linearGradient>
            <mask id="opacity">
                <rect x="50" y="0" width="600" height="400" fill="green"></rect>
            </mask>
        </defs>
        <rect id="back" x="0" y="0" width="400" height="300" fill="#d4fcff"></rect>
        <rect id="front" x="0" y="0" width="400" height="300" fill="#fcd3db" mask="url(#opacity)"></rect>
    </svg>
复制代码

该代码的展现效果为: svg

aaaaaaaaaaaa
当我把第一个svg的mask标签删除以后

<svg width="400" height="300">
        <defs>
            <linearGradient id='white2black'>
                <stop offset="0" stop-color="white"></stop>
                <stop offset="100%" stop-color="black"></stop>
            </linearGradient>
        </defs>
        <rect id="back" x="0" y="0" width="400" height="300" fill="#d4fcff"></rect>
        <rect id="front" x="0" y="0" width="400" height="300" fill="#fcd3db" mask="url(#opacity)"></rect>
    </svg>
    <svg width="600" height="300">
        <defs>
            <linearGradient id='white2black'>
                <stop offset="0" stop-color="blue"></stop>
                <stop offset="50%" stop-color="black"></stop>
                <stop offset="100%" stop-color="green"></stop>
            </linearGradient>
            <mask id="opacity">
                <rect x="50" y="0" width="600" height="400" fill="green"></rect>
            </mask>
        </defs>
        <rect id="back" x="0" y="0" width="400" height="300" fill="#d4fcff"></rect>
        <rect id="front" x="0" y="0" width="400" height="300" fill="#fcd3db" mask="url(#opacity)"></rect>
    </svg>
复制代码

效果变为: 测试

aaaaaaaaaaaa
至此,真相已经付出了水面。svg在寻找重用元素时的机制为:在当前HTML环境中寻找第一个匹配的元素。并非咱们想象中的在svg本身内部寻找或者逐层往外寻找。因此,在html中直接引入svg必然会存在互相影响的问题,也必然会带来一些未知的风险。那么到底有没有较为安全的方案呢?我以为方法总比困难多,解决方案确定是存在的。为了防止互相影响,咱们能够采用使用css中的background-image属性将svg引入,该方案中的svg在寻找重用元素时,仅仅会寻找自身标签内是否存在,而不会向外寻找,所以必定程度上保证了svg图标的安全性。那么咱们之后要一直使用该种方案吗?我以为仍是分场景使用最为合适。例如:一成不变的svg图标能够采用css的方式引入,带有交互行为的图标能够采用html的方式引入,方便修改样式。固然,我以为最重要的就是,对于咱们直接使用的图标,svg内最好干净的仅剩下path标签,这样不会带来任何问题。

一样,svg sprites使用use引入一样存在问题。。

相关文章
相关标签/搜索