原文: SVG 优化探索 | AlloyTeam
作者: summer
在前端开发中或多或少都有用到 SVG,本篇文章就来总结下如何在前端项目中使用 SVG,每种使用方式的优缺点分析,以及对 SVG 的一些优化探索。
一、认识 SVG
SVG(Scalable Vector Graphics,可缩放矢量图形)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的一种图形格式。
这里简要说明下位图与矢量图的区别:位图是由像素点构成的,矢量图则是由一些形状元素构成。下图中放大位图可以看到点,而放大矢量图看到的仍然是形状。SVG 属于矢量图,因此理论上能够无限缩放,而不会导致马赛克。
简单的 SVG 样式:
SVG 的几大特点:
1)能使用 CSS/JS 进行控制。
2)与分辨率无关,在任何分辨率的设备上都能清晰地展示。就不需要为高清屏准备二倍图、三倍图了。
3)容易编辑
4)高度可压缩
来看下 SVG 格式与 JPG、GIF、PNG 图片格式在使用上的区别:
二、SVG 使用方法
使用 SVG 图片有多种方式,每种方式都有自己的优缺点,选择合适的方式就好。
1、在 Img 中引入
需要注意的是,使用这种方法在交互性上有很多限制,如不能使用 JS 来控制,不支持 CSS3 动画。
2、通过 CSS 中 Background-image 引入
.logo {
background-image: url(logo.svg);
}
使用背景图片方法需要注意的一点是,最好不要使用 base64 编码来格式化 SVG 图片,因为它在加载完前会阻塞其它资源的下载。使用这种方法在交互性上也有很多限制,如不能使用 JS 来控制,不支持 CSS3 动画。
3、通过 Iframe 引入
不确定这是不是还是一种好的使用方法。
4、通过 Embed 引入
大多数浏览器都支持,但最好还是不要使用这种方法。
5、通过 Object 引入
如果你想使用 JS 来进行交互控制,是 SVG 使用方法中很好的一个选择。 只需要把它放到 HTML 中就可以了。
6、将 SVG 元素直接加入到 HTML 中
把 SVG直接插入到 HTML 中,可以节省 HTTP 请求,而且很方便使用 JS 来控制。但是这样,SVG资源就不能被浏览器缓存。同时使用 JS 来操控 SVG 会发生重绘行为。
这几种使用方式的特点:
三、在 React + Webpack 项目中如何引入 SVG?
在 webpack 中有各种 loader 可以加载 SVG:
1、url-loader
官方文档:https://webpack.docschina.org...
可以把 svg 当作普通 jpg、png 图片来使用。
安装:
npm install url-loader --save-dev
webpack 配置:
test: /\.(png|jpg|gif|svg)$/i,
use: [
{
loader: 'url-loader',
}
]
在 React 组件中的引入方式:
import test from './test.svg'
export default class App extends Component {
render() {
return (
);
}
};
经过 url-loader 处理后,test.svg 文件被处理为 "data:image/svg+xml;base64,PHN2ZyBpZ....." 这样的 base64 编码。
module.exports = "data:image/svg+xml;base64,PHN2ZyBpZ..
我们用 webpack-bundle-analyzer 插件测试看看 svg 文件被打包后的大小:
此外 file-loader 也可以。
这种方式的优点: SVG 资源可被缓存,SVG 资源可单独加载。
缺点:加载多个 SVG 文件时,会产生多个 http 请求,影响页面加载性能。
2、svg-react-loader
安装:
npm install svg-react-loader --save-dev
webpack 配置:
{
test: /\.svg$/,
loader: 'svg-react-loader',
}
在 React 组件中的引入方式:
import Test from "./svg/test.svg";
export default class App extends Component {
render() {
return
}
}
svg-react-loader 会将 svg 文件处理成 React 组件,最后会以 svg 标签的形式渲染到 html 中。从最后渲染到 html 中的代码来看,svg-react-loader 是有对 svg 原文件进行优化的。从打包后的文件大小可以看出来文件有被压缩:
这种方式的缺点:SVG 资源不可被缓存。且会将 svg 资源的处理逻辑与页面的交互逻辑一起打包。
最好的方式是:SVG 资源与 JS 资源分离,图片的加载不影响页面的主要执行逻辑。并且所有的 SVG 资源希望能一次 HTTP 请求就能搞定。
3、svg-inline-loader
官方文档:https://webpack.js.org/loader...
svg-inline-loader 会根据配置项去除 SVG 中冗余的代码或者不必要的代码,以减少 SVG 的文件大小。
安装:
npm install svg-inline-loader --save-dev
webpack 配置:
{
test: /\.svg$/,
loader: 'svg-inline-loader'
}
在 React 组件中的引入方式:
import test from "./test.svg";
export default class App extends Component {
render() {
return (
);
}
};
经过 svg-inline-loader 处理后 svg 文件被处理为这样的字符串:
module.exports = \"
看下打包大小,相对比,这个体积优化的效果还不错。
此外还有 raw-loader 等都可以处理 SVG 文件。
四、SVG 优化
1. SVG 文件压缩
一般设计师会使用 Adobe suite 或者 Sketch 等工具导出 SVG 文件,但这样的 SVG 一般是有属性冗余的,所以需要对 SVG 进行一定的压缩。
手动压缩:当然,也不需要手动压缩,但是可以看看哪些属性是有冗余的。
工具压缩:推荐使用 SVGO。
webpack 项目中引入 SVGO:
安装:
npm install svgo svgo-loader --save-dev
webpack 配置:
{
test: /\.svg$/,
exclude: /node_modules/,
loaders: [
'url-loader',
'svgo-loader'
],
},
在单独使用 url-loader 时,文件 test.svg 打包后的大小是:17.82 KB。在使用 svgo-loader 后,我们看下打包大小,确实是有很大幅度的压缩。
2. SVG 雪碧图
当项目需要加载多个 SVG 文件时,上述加载方式就需要优化了。我们考虑以下几种情况:
1)使用 url-loader 加载多个 svg 文件
这种方式会产生多次 http 请求,而浏览器对并发请求数目是有限制的,请求太多会影响页面加载性能。这种情况就需要引入雪碧图,将多个 svg 文件合成一个 svg 文件。
2)使用 svg-react-loader ,当一个组件需要加载多个 svg 文件时,所有的 svg 文件都会被打包到 index.js 文件中。如果 svg 文件过多,就会增大 index.js 文件体积。而 SVG 作为图片资源,最好是作为一个单独文件异步加载,与页面主要执行逻辑分离。当然这里也同样需要引入雪碧图。
第一种方法
第一种方法是把所有的图标通过
元素定义在 SVG 代码中,嵌入在
元素中的图标是不会被直接显示的。通过使用元素的 xlink:href="#id" 来引用单个图标。
也就是说合成后的大 SVG 会是这样:
在需要引入单个图标的时候:
这里我们使用 svg-sprite-loader 来自动生成 svg 雪碧图。
安装:
npm install svg-sprite-loader --save-dev
loader 配置:
{
test: /\.svg$/,
exclude: /node_modules/,
use: [{
loader: 'svg-sprite-loader',
}],
}
react 组件中引入:
import "./svg/node.svg";
import "./svg/test.svg";
import "./svg/logo.svg";
export default class App extends Component {
render() {
return (
);
}
}
这样配置打包后,body 下会自动注入一个大的 SVG 元素,达到了雪碧图的效果。可是这样还是有两个缺点:1)当需要使用这些 svg 的时候,需要在组件中单独 import。2)这个大的 SVG 资源并没有与 JS 资源分离。
为了解决第一个缺点:在组件加载前,增加自动 import 所有 svg 的功能:
// requires and returns all modules that match
const requireAll = requireContext => requireContext.keys().map(requireContext);
// import all svg
const req = require.context('./svg', true, /\.svg$/);
requireAll(req);
现在可以自动导入了,不用到每个组件中去单独 import了。可是还需要解决第二个问题:SVG 资源与 JS 资源分离。
从 svg-sprite-loader 文档中看到可以利用 spriteloaderplugin 插件将所有 SVG 文件打包输出为一个大的 SVG:
这里的 loader 配置:
{
test: /\.svg$/,
exclude: /node_modules/,
loaders: [
{
loader: 'svg-sprite-loader',
options: {
extract: true,
spriteFilename: 'sprite.svg', // 生成的 SVG 雪碧图资源路径
}
},
'svgo-loader'
],
},
插件配置:
plugins: [
new SpriteLoaderPlugin({
plainSprite: true
})
]
这种方式会在打包目录下生成 sprite.svg 文件,我们可以通过 ajax 请求的方式获取到该 svg 资源,然后将其注入到 html 中去,这样就将 svg 资源与 js 资源分离了。
fetch(`sprite.svg`).then(res => {
return res.text();
}).then(data => {
document.getElementById('svg-sprite').innerHTML = data;
});
这样的方式是我们自己手动去请求的,思考下能否使用 webpack 配置或者一些插件就能达到 svg 与 js 资源分离的目的呢。于是想到了webpack 的 SplitChunksPlugin 插件提供的抽取公共代码的能力。
于是尝试把 import 所有 svg 的逻辑抽离出来成为一个单独的 js 文件。配置好 SplitChunks 后,webpack 就会把这部分逻辑抽离出来,单独打包成一个 js 文件。这样就实现了 svg 与 js 资源分离了。
webpack 配置:
optimization: {
splitChunks: {
cacheGroups: {
commons: {
name: 'commons',
chunks: 'initial',
minChunks: 2
}
}
}
},
第二种方法
第二种方法是使用 SVG 的 viewbox 属性来指定显示 SVG 画布的区域,跟 background-position 原理差不多。待探索~
参考:
https://svgontheweb.com/
https://developer.mozilla.org...
https://www.sumydesigns.com/d...
https://community.wia.io/d/6-...
AlloyTeam 欢迎优秀的小伙伴加入。
简历投递: [email protected]
详情可点击 腾讯AlloyTeam招募Web前端工程师(社招)