SVG 優化探索

原文: SVG 優化探索 | AlloyTeam
做者: summer

在前端開發中或多或少都有用到 SVG,本篇文章就來總結下如何在前端項目中使用 SVG,每種使用方式的優缺點分析,以及對 SVG 的一些優化探索。css

1、認識 SVG

SVG(Scalable Vector Graphics,可縮放矢量圖形)是一種基於可擴展標記語言XML),用於描述二維矢量圖形的一種圖形格式。html

這裏簡要說明下位圖與矢量圖的區別:位圖是由像素點構成的,矢量圖則是由一些形狀元素構成。下圖中放大位圖能夠看到點,而放大矢量圖看到的仍然是形狀。SVG 屬於矢量圖,所以理論上可以無限縮放,而不會致使馬賽克。前端

image-20190721201713555

簡單的 SVG 樣式:node

<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 的幾大特色:react

1)能使用 CSS/JS 進行控制。webpack

2)與分辨率無關,在任何分辨率的設備上都能清晰地展現。就不須要爲高清屏準備二倍圖、三倍圖了。web

3)容易編輯ajax

4)高度可壓縮npm

來看下 SVG 格式與 JPG、GIF、PNG 圖片格式在使用上的區別:json

image-20190728170717428

2、SVG 使用方法

使用 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 會發生重繪行爲。

這幾種使用方式的特色:

image-20190728170355429

3、在 React + Webpack 項目中如何引入 SVG?

在 webpack 中有各類 loader 能夠加載 SVG:

一、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 (
       <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 文件被打包後的大小:

image-20190727213907016

此外 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 原文件進行優化的。從打包後的文件大小能夠看出來文件有被壓縮:

image-20190727214315507

這種方式的缺點:SVG 資源不可被緩存。且會將 svg 資源的處理邏輯與頁面的交互邏輯一塊兒打包。

最好的方式是:SVG 資源與 JS 資源分離,圖片的加載不影響頁面的主要執行邏輯。而且全部的 SVG 資源但願能一次 HTTP 請求就能搞定。

三、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 (
        <span dangerouslySetInnerHTML={{__html: test}} />
    );
  }
};

通過 svg-inline-loader 處理後 svg 文件被處理爲這樣的字符串:

module.exports = \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\">.....

看下打包大小,相對比,這個體積優化的效果還不錯。

image-20190727220013803

此外還有 raw-loader 等均可以處理 SVG 文件。

4、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 後,咱們看下打包大小,確實是有很大幅度的壓縮。

image-20190727222022385

2. SVG 雪碧圖

當項目須要加載多個 SVG 文件時,上述加載方式就須要優化了。咱們考慮如下幾種狀況:

1)使用 url-loader 加載多個 svg 文件

image-20190727230155985

這種方式會產生屢次 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 資源分離。

image-20190728141342715

image-20190728142106996

爲了解決第一個缺點:在組件加載前,增長自動 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 資源分離了。

image-20190731212610492

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 歡迎優秀的小夥伴加入。
簡歷投遞: alloyteam@qq.com
詳情可點擊 騰訊AlloyTeam招募Web前端工程師(社招)

clipboard.png

相關文章
相關標籤/搜索