背景
最近搞了一个项目,基于React,用了自己的脚手架fdt。
svg的使用
项目就不上图了,总之其中存在各式各样的图标。最终选择了svg来控制图标的显示,原因主要有如下几方面:
- 相比传统的图片,尺寸更小,可压缩性更强
- 可伸缩,更清晰
- 方便读取和修改
- 设计软件直接导出
svg的使用方式多种多样,适合自己的才是最好的。下面简单介绍下我们的项目如何在fdt脚手架中使用了svg。因为fdt基于webpack打包,所以webpack中必不可少需要增加针对svg的配置。代码如下:
// 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返回的是一个包含attributes和content的对象,我们无法直接使用。处理后的结果如下代码所示:
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格式。代码如下:
// 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来控制各式各样图标的显示。
遇到的问题

<g>该标签代表组合<defs>定义重用图形<polygon>定义多边形<mask>定义蒙层<use>实现SVG现有图形的重用
既然无法直接找到答案,那只好上排除法来寻找问题所在了。最后发现,问题出现的原因是我们新引入的图标影响了原有图标。svg互相影响也真的让我非常震惊。
那到底是怎么互相影响的呢?原因就是新的图标中定义了一个mask蒙层,属性id为mask-2。受影响的图标中,path标签的mask属性引用了该mask-2的蒙层,导致新图标的出现影响了部分旧图标。
那么对于直接在html中引入svg,浏览器对于重用图标的寻找机制是怎么样的呢?我们做了如下测试:
<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 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>
效果变为:

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