SVG Sprite 简介

前言

  首页的高速加载和渲染一直是前端开发者们津津乐道的事情,因此各种技术也应运而生。在 HTTP1.1 时代,为了减少请求的发送,加快首页加载,压缩和合并成了必不可少的技术,其中包括了 JavaScript 文件的压缩、混淆和合并,还有 CSS 文件的压缩和合并,最后还有一个是针对小图片的请求优化,也就是 CSS Sprite,也叫 雪碧图CSS 精灵。其大概的思想就是将多个小图片按照一定的尺寸和位置排列好,然后合成一张图片,最后用户访问页面时,只要请求这一张合成图,而开发者利用 background-position 等属性控制显示合成图某个位置的图片,即可达到一张图片多个图标的效果,同时也将请求数量压缩为一个。当然,这次我们要说的并不是这个技术,而是与之运用思想类似的 SVG Sprite

SVG Sprite

  SVG Sprite 使用 <symbol> 标签来定义一个图形模板对象,好处在于其可以重复利用,我们可以看一下 MDN 中对 <symbol> 的定义:

symbol元素用来定义一个图形模板对象,它可以用一个元素实例化。symbol元素对图形的作用是在同一文档中多次使用,添加结构和语义。结构丰富的文档可以更生动地呈现出来,类似讲演稿或盲文,从而提升了可访问性。注意,一个symbol元素本身是不呈现的。只有symbol元素的实例(亦即,一个引用了symbol的 元素)才能呈现。

  可以看到,<symbol> 定义的图形并不会第一时间显示出来,只有使用了 <use> 标签进行实例化以后才会显现。MDN 中对其做出了如下定义:

use元素在SVG文档内取得目标节点,并在别的地方复制它们。它的效果等同于这些节点被深克隆到一个不可见的DOM中,然后将其粘贴到use元素的位置,很像HTML5中的克隆模板元素。

  而要使用 <use> 来实例化一个 svg图形模板对象,则要使用其中的 xlink:href 属性,在我们处理好的 <symbol> 上都会带有一个 id,如下所示(伪代码):

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0;visibility:hidden">  
  <defs>
    <symbol id="icon1">...</symbol>
    <symbol id="icon2">...</symbol>
    <symbol id="icon3">...</symbol>
  </defs>
</svg>  

  根据每一个 <symbol>id,我们可以使用 <use> 根据这些 id 来使用 svg,如下所示:

<div class="icons">  
  <svg><use xlink:href="#icon1"/></svg>
  <svg><use xlink:href="#icon2"/></svg>
  <svg><use xlink:href="#icon3"/></svg>
</div>  

  SVG Sprite 的基本原理就是运用这些元素,相比较 CSS SpriteSVG Sprite 显得更为友好,不用多余的例如 background-position 属性来控制位置。

兼容性

  运用一个技术的前提都是其兼容性满足项目的最低要求,或者在不兼容的情况下有相对应的替代方案。在饿了么 Web 的兼容性要求为 PC 端 IE9+,安卓移动端 4.4+,IOS7+,具体可以看 ElemeFE 的 style-guide。下面是在 caniuse 上的结果: SVG-兼容性

  可以看到兼容性在 PC 端上是完全没有问题的,而在移动端上也能支持,所以可以安心地用起来了。

结合 Webpack 使用 SVG Sprite

  我们使用 Webpack 来对多个分离的 SVG 文件进行自动化处理为合并好的多个 <symbol>,并插入到 <body> 顶部。首先我们要对 webpack.config.js 进行配置。

首先看一下基本的文件目录结构:

├── LICENSE
├── README.md
├── css
│   └── index.css
├── dist
│   ├── app.css
│   ├── app.js
│   └── index.html
├── js
│   └── index.js
├── package.json
├── static
│   ├── analytics.svg
│   ├── archives.svg
│   ├── businessman.svg
│   ├── businessmen.svg
│   ├── certificate.svg
│   ├── chat.svg
│   └── contract.svg
├── template
│   └── index.html
└── webpack.config.js

webpack.config.js

const path = require('path');  
const webpack = require('webpack');  
const ExtractTextPlugin = require('extract-text-webpack-plugin');  
const HtmlWebpackPlugin = require('html-webpack-plugin');

var plugins = [  
  new HtmlWebpackPlugin({
    filename: './index.html',
    template: './template/index.html'
  })
];

plugins.push(  
  new ExtractTextPlugin('[name].css')
);

module.exports = {  
  entry: {
    app: './js/index.js',
  },

  output: {
    path: './dist',
    publicPath: './',
    // filename: '[name].[chunkhash:6].js'
    filename: '[name].js'
  },

  resolve: {
    extensions: [ '', '.js' ]
  },

  module: {
    loaders: [
      { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' },
      { test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css') },
      { test: /\.svg$/, loaders: [ 'svg-sprite-loader', 'svgo-loader?useConfig=svgoConfig' ] },
      { test: /\.(gif|png|jpg|ttf|woff2|woff|eot)$/, loader: 'url?limit=1000&name=[path][name].[hash:6].[ext]' }
    ]
  },

  svgoConfig: {
    plugins: [
      { removeTitle: true },
      { convertColors: { shorthex: true } },
      { convertPathData: true },
      { cleanupAttrs: true },
      { removeComments: true },
      { removeDesc: true },
      { removeUselessDefs: true },
      { removeEmptyAttrs: true },
      { removeHiddenElems: true },
      { removeEmptyText: true }
    ]
  },
  plugins: plugins
};

  可以看到,配置文件中使用了 svg-sprite-loadersvgo-loadersvg 文件进行处理,svg-sprite-loader 的作用就是将多个 svg 文件合并为一个 <svg> 元素。至于 svgo-loader,作用是将 <svg> 中一些无用的信息过滤去除,精简结构,详细配置可以自行查阅对应的文档说明,可以根据实际需求进行过滤。接下来将列出 index.css, index.js, index.html 的内容:

index.css

* {
  box-sizing: border-box;
}

.icons svg {
  width: 100px;
  height: 100px;
}

index.js

import indexStyle from '../css/index.css';  
import analytics from '../static/analytics.svg'  
import archives from '../static/archives.svg'  
import businessman from '../static/businessman.svg'  
import businessmen from '../static/businessmen.svg'  
import certificate from '../static/certificate.svg'  
import chat from '../static/chat.svg'  
import contract from '../static/contract.svg'

console.log('demo complete');  

index.html

<!DOCTYPE html>  
<html>  
  <head>
    <meta charset="utf-8">
    <title>svg-sprites-demo</title>
  </head>
  <body>
    <div class="icons">
      <svg><use xlink:href="#analytics"/></svg>
      <svg><use xlink:href="#archives"/></svg>
      <svg><use xlink:href="#businessman"/></svg>
      <svg><use xlink:href="#businessmen"/></svg>
      <svg><use xlink:href="#certificate"/></svg>
      <svg><use xlink:href="#chat"/></svg>
      <svg><use xlink:href="#contract"/></svg>
    </div>
  </body>
</html>  

  以上就是主要的一些配置和内容,如果需要完整的项目,可以到我的 github 下 clone 项目到你的本地进行构建。下面是构建后的网页效果和结构:

效果

优点 & 缺点

优点: * 将多个请求压缩为无请求 * svg 对比 image 其屏幕适应性更好,任何分辨率都能达到高清效果 * svg 体积更小 * 每一个 <symbol> 都可以重复利用

缺点: * svg 不利于变动性大的图片,例如需要经常修改颜色 * 兼容性对于需要兼容 IE8- 的网站不好,需要对低版本浏览器有替代方案

参考资料:

https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/symbol
https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/defs
https://developer.mozilla.org/zh-CN/docs/Web/SVG/Element/use