原文:SVG 優化探索 | AlloyTeam
做者:summercss
在前端開發中或多或少都有用到 SVG,本篇文章就來總結下如何在前端項目中使用 SVG,每種使用方式的優缺點分析,以及對 SVG 的一些優化探索。html
SVG(Scalable Vector Graphics,可縮放矢量圖形)是一種基於可擴展標記語言(XML),用於描述二維矢量圖形的一種圖形格式。前端
這裏簡要說明下位圖與矢量圖的區別:位圖是由像素點構成的,矢量圖則是由一些形狀元素構成。下圖中放大位圖能夠看到點,而放大矢量圖看到的仍然是形狀。SVG 屬於矢量圖,所以理論上可以無限縮放,而不會致使馬賽克。node
簡單的 SVG 樣式:react
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="#000" fill-rule="evenodd" d="M8.667 8L12 11.333l-.667.667L8 8.667 4.667 12 4 11.333 7.333 8 4 4.667 4.667 4 8 7.333 11.333 4l.667.667z" opacity=".64"/>
</svg>
複製代碼
SVG 的幾大特色:webpack
1)能使用 CSS/JS 進行控制。web
2)與分辨率無關,在任何分辨率的設備上都能清晰地展現。就不須要爲高清屏準備二倍圖、三倍圖了。ajax
3)容易編輯npm
4)高度可壓縮json
來看下 SVG 格式與 JPG、GIF、PNG 圖片格式在使用上的區別:
使用 SVG 圖片有多種方式,每種方式都有本身的優缺點,選擇合適的方式就好。
一、在 Img 中引入
<img src="logo.svg" alt="Logo" height="65" width="68">
複製代碼
須要注意的是,使用這種方法在交互性上有不少限制,如不能使用 JS 來控制,不支持 CSS3 動畫。
二、經過 CSS 中 Background-image 引入
.logo {
background-image: url(logo.svg);
}
複製代碼
使用背景圖片方法須要注意的一點是,最好不要使用 base64 編碼來格式化 SVG 圖片,由於它在加載完前會阻塞其它資源的下載。使用這種方法在交互性上也有不少限制,如不能使用 JS 來控制,不支持 CSS3 動畫。
三、經過 Iframe 引入
<iframe src="logo.svg">Your browser does not support iframes</iframe>
複製代碼
不肯定這是否是仍是一種好的使用方法。
四、經過 Embed 引入
<embed type="image/svg+xml" src="logo.svg" />
複製代碼
大多數瀏覽器都支持,但最好仍是不要使用這種方法。
五、經過 Object 引入
<object type="image/svg+xml" data="bblogo.svg">Your browser does not support SVGs</object>
複製代碼
若是你想使用 JS 來進行交互控制,<object>
是 SVG 使用方法中很好的一個選擇。 只須要把它放到 HTML 中就能夠了。
六、將 SVG 元素直接加入到 HTML 中
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 68 65">
<path fill="#1A374D" d="M42 27v-20c0-3.7-3.3-7-7-7s-7 3.3-7 7v21l12 15-7 15.7c14.5 13.9 35 2.8 35-13.7 0-13.3-13.4-21.8-26-18zm6 25c-3.9 0-7-3.1-7-7s3.1-7 7-7 7 3.1 7 7-3.1 7-7 7z"/>
</svg>
複製代碼
把 SVG直接插入到 HTML 中,能夠節省 HTTP 請求,並且很方便使用 JS 來控制。可是這樣,SVG資源就不能被瀏覽器緩存。同時使用 JS 來操控 SVG 會發生重繪行爲。
這幾種使用方式的特色:
在 webpack 中有各類 loader 能夠加載 SVG:
一、url-loader
官方文檔:webpack.docschina.org/loaders/url…
能夠把 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 (
<img src={test} />
);
}
};
複製代碼
通過 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 請求,影響頁面加載性能。
二、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 <Test className={iconType} />
}
}
複製代碼
svg-react-loader 會將 svg 文件處理成 React 組件,最後會以 svg 標籤的形式渲染到 html 中。從最後渲染到 html 中的代碼來看,svg-react-loader 是有對 svg 原文件進行優化的。從打包後的文件大小能夠看出來文件有被壓縮:
這種方式的缺點:SVG 資源不可被緩存。且會將 svg 資源的處理邏輯與頁面的交互邏輯一塊兒打包。
最好的方式是:SVG 資源與 JS 資源分離,圖片的加載不影響頁面的主要執行邏輯。而且全部的 SVG 資源但願能一次 HTTP 請求就能搞定。
三、svg-inline-loader
官方文檔:webpack.js.org/loaders/svg…
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 (
<span dangerouslySetInnerHTML={{__html: test}} />
);
}
};
複製代碼
通過 svg-inline-loader 處理後 svg 文件被處理爲這樣的字符串:
module.exports = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\">.....
複製代碼
看下打包大小,相對比,這個體積優化的效果還不錯。
此外還有 raw-loader 等均可以處理 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 後,咱們看下打包大小,確實是有很大幅度的壓縮。
當項目須要加載多個 SVG 文件時,上述加載方式就須要優化了。咱們考慮如下幾種狀況:
1)使用 url-loader 加載多個 svg 文件
這種方式會產生屢次 http 請求,而瀏覽器對併發請求數目是有限制的,請求太多會影響頁面加載性能。這種狀況就須要引入雪碧圖,將多個 svg 文件合成一個 svg 文件。
2)使用 svg-react-loader ,當一個組件須要加載多個 svg 文件時,全部的 svg 文件都會被打包到 index.js 文件中。若是 svg 文件過多,就會增大 index.js 文件體積。而 SVG 做爲圖片資源,最好是做爲一個單獨文件異步加載,與頁面主要執行邏輯分離。固然這裏也一樣須要引入雪碧圖。
第一種方法是把全部的圖標經過<symbol>
元素定義在 SVG 代碼中,嵌入在<symbol>
元素中的圖標是不會被直接顯示的。經過使用<use>
元素的 xlink:href="#id" 來引用單個圖標。
也就是說合成後的大 SVG 會是這樣:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="__SVG_SPRITE_NODE__">
<symbol class="icon" viewBox="0 0 1024 1024" id="icon名1">{{省略的icon path}}</symbol>
<symbol class="icon" viewBox="0 0 1024 1024" id="icon名2">{{省略的icon path}}</symbol>
</svg>
複製代碼
在須要引入單個圖標的時候:
<use xlink:href="#symbolId"></use>
複製代碼
這裏咱們使用 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 (
<div>
<svg><use xlinkHref="#node" /></svg>
<svg><use xlinkHref="#test" /></svg>
<svg><use xlinkHref="#logo" /></svg>
</div>
);
}
}
複製代碼
這樣配置打包後,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 原理差很少。待探索~
參考:
developer.mozilla.org/zh-CN/docs/…
www.sumydesigns.com/difference-…
AlloyTeam 歡迎優秀的小夥伴加入。
簡歷投遞: alloyteam@qq.com
詳情可點擊 騰訊AlloyTeam招募Web前端工程師(社招)