# 深入 svg

文档 (opens new window)

# 理解命名空间

使用 xmlns 来指定命名空间(xml namespace),用于告诉解析器在这个 xml 标签内执行的是什么标准,不同的标准有着其特有的标签集合和属性,如下:

分别指定 html 和 svg 的命名空间。

<html xmlns="http://www.w3.org/1999/xhtml">
  <body>
    <!-- 在这里放置一些 XHTML 标签 -->
    <svg xmlns="http://www.w3.org/2000/svg" width="300px" height="200px">
      <!-- 在这里放置一些 SVG 标签 -->
    </svg>
    <!-- 在这里放置一些 XHTML 标签 -->
  </body>
</html>

也相当于

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:svg="http://www.w3.org/2000/svg">
  <body>
    <h1>SVG embedded inline in XHTML</h1>
    <svg:svg width="300px" height="200px">
      <svg:circle cx="150" cy="100" r="50" fill="#ff0000"/>
    </svg:svg>
  </body>
</html>

# 扩展命名空间

扩展命名空间,是命名空间的子集。标签有其执行标准,属性也有其执行标准,扩展命名空间一般就是指定某个属性的执行标准,如下:

  1. 使用 xmlns 指定了命名空间,
  2. 使用 xmlns:xlink 指定了 xlink 属性的执行标准,
  3. xlink:href 使用这个属性,它遵循上述执行标准
<svg xmlns="http://www.w3.org/2000/svg"
     xmlns:xlink="http://www.w3.org/1999/xlink">
  <script xlink:href="cool-script.js" type="text/ecmascript"/>
</svg>

# <symble> 和 <use>

  1. symble 定义一个图形对象,但它本身是不显示的。
  2. use 引用并实例化刚定义的图形对象,它显示。

如下:(注意 xlink:href 的值为 symble.id的值)

<svg>
<!-- symbol definition  NEVER draw -->
<symbol id="sym01" viewBox="0 0 150 110">
  <circle cx="50" cy="50" r="40" stroke-width="8" stroke="red" fill="red"/>
  <circle cx="90" cy="60" r="40" stroke-width="8" stroke="green" fill="white"/>
</symbol>

<!-- actual drawing by "use" element -->
<use xlink:href="#sym01"
     x="0" y="0" width="100" height="50"/>
<use xlink:href="#sym01"
     x="0" y="50" width="75" height="38"/>
<use xlink:href="#sym01"
     x="0" y="100" width="50" height="25"/>
</svg>

  1. 它不仅可以在单个 svg标签中完成定义和引用,还可以引用别的 svg所定义的内容,iconfont 正是利用了这点特性。

# iconfont.js 干了什么?

iconfont.js 是引入 svg 文件到网页的方法之一。它的原理就是用 symble将一堆 svg 定义为模板对象,并插入到文档中。当要使用 svg 时,使用 use标签对其引用。

// Iconfont SVG 字符串,这里有两个图标,每个symble对应一个
window.iconfontSvgString =
  '<svg><symbol id="icon-SEND" viewBox="0 0 1024 1024"><path d="M633.6 938.666667c-8.533333 0-17.066667-4.266667-19.2-12.8l-157.866667-356.266667-358.4-160c-8.533333-4.266667-12.8-10.666667-12.8-19.2s6.4-17.066667 14.933334-19.2l810.666666-283.733333c8.533333-2.133333 17.066667 0 21.333334 4.266666s8.533333 14.933333 4.266666 21.333334l-283.733333 810.666666c-2.133333 8.533333-8.533333 14.933333-19.2 14.933334z m-469.333333-546.133334L480 533.333333c4.266667 2.133333 8.533333 6.4 10.666667 10.666667l140.8 315.733333L883.2 140.8 164.266667 392.533333z" fill="#333333" ></path><path d="M471.466667 573.866667c-6.4 0-10.666667-2.133333-14.933334-6.4-8.533333-8.533333-8.533333-21.333333 0-29.866667L902.4 91.733333c8.533333-8.533333 21.333333-8.533333 29.866667 0s8.533333 21.333333 0 29.866667L486.4 567.466667c-4.266667 4.266667-8.533333 6.4-14.933333 6.4z" fill="#333333" ></path></symbol><symbol id="icon-messagexinxi" viewBox="0 0 1024 1024"><path d="M303.845193 890.605897c-2.653433 0-4.635577-0.669242-5.957689-1.991354-6.618744-2.644223-9.93221-7.236821-9.93221-13.897521L287.955294 731.715097 129.064494 731.715097c-18.541285 0-33.760918-5.965875-45.684482-17.880229C71.464635 701.919491 65.50797 686.733627 65.50797 668.158572L65.50797 191.484126c0-18.541285 5.957689-33.770128 17.872043-45.684482 11.923564-11.914354 27.143197-17.872043 45.684482-17.872043l762.678296 0c18.533099 0 33.760918 5.957689 45.684482 17.872043 11.914354 11.914354 17.872043 27.143197 17.872043 45.684482l0 476.673423c0 18.575054-5.957689 33.760918-17.872043 45.676295-11.923564 11.914354-27.151383 17.880229-45.684482 17.880229L484.582557 731.714074 313.77638 886.631376C311.12397 889.317555 307.818691 890.605897 303.845193 890.605897zM129.064494 159.706375c-21.185508 0-31.777751 10.627035-31.777751 31.777751l0 476.673423c0 21.185508 10.592242 31.778774 31.777751 31.778774l174.780699 0c10.593266 0 15.888875 5.296633 15.888875 15.888875l0 123.138528 148.959613-135.062092c2.65241-2.644223 5.957689-3.966335 9.93221-3.966335l413.116899 0c21.185508 0 31.778774-10.593266 31.778774-31.778774L923.521564 191.484126c0-21.151739-10.593266-31.777751-31.778774-31.777751L129.064494 159.706375z" fill="#231815" ></path></symbol></svg>';

// 在页面中插入 Iconfont SVG
(function(global) {
  var script = (scripts = document.getElementsByTagName("script"))[
      scripts.length - 1
    ],
    injectCss = script.getAttribute("data-injectcss"),
    disableInjectSvg = script.getAttribute("data-disable-injectsvg");

  if (!disableInjectSvg) {
    var insertSvg = function(svg, el) {
      el.parentNode.insertBefore(svg, el);
    };
    if (injectCss && !global.__iconfont_svg_css_inject__) {
      global.__iconfont_svg_css_inject__ = true;
      try {
        document.write(
          "<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>"
        );
      } catch (e) {
        console && console.log(e);
      }
    }
    var injectSvg = function() {
      var container = document.createElement("div");
      container.innerHTML = global.iconfontSvgString;
      var svg = container.getElementsByTagName("svg")[0];
      if (svg) {
        svg.setAttribute("aria-hidden", "true");
        svg.style.position = "absolute";
        svg.style.width = 0;
        svg.style.height = 0;
        svg.style.overflow = "hidden";
        var el = document.body.firstChild;
        el ? insertSvg(svg, el) : document.body.appendChild(svg);
      }
    };
    if (document.addEventListener) {
      ~["complete", "loaded", "interactive"].indexOf(document.readyState)
        ? setTimeout(injectSvg, 0)
        : document.addEventListener("DOMContentLoaded", injectSvg, false);
    } else if (document.attachEvent) {
      var onStateChange = function() {
        "complete" == document.readyState &&
          (document.detachEvent("onreadystatechange", onStateChange),
          injectSvg());
      };
      document.attachEvent("onreadystatechange", onStateChange);
    }
  }
})(window);

# vite-svg-plugin

在 webpack 时通常是使用 svg-sprite-loader 完成 svg 的导入和 symble 工作,而这个插件在 vite 中好像无法使用。

可以选择使用 vite-svg-plugin插件,虽然用户比较少,但作者热心地放了开发过程,可以放心使用。 npm: vite-svg-plugin (opens new window) github: vite-svg-plugin (opens new window) juejin: vite-svg-plugin (opens new window)

在vite.config.js中配置:

// 插件配置
plugins: [
  svgBuilder({
    path: resolve(__dirname, "../src/assets/svg") + "/",// svg文件夹路径
    prefix: "icon",
  }), // use xlink:href = #{prefix}-{文件名}
];

# 配套写一个 Icon 组件

Icon.vue

<template>
  <i :class="['svg-icon', `svg-icon-${name}`, className]" :style="svgStyle">
    <svg fill="currentColor" stroke-width="2" aria-hidden="true" width="100%" height="100%">
      <use :xlink:href="iconName" />
    </svg>
  </i>
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
  name: "icon",
  props: {
    name: {
      // 图标名,如 fe-search
      type: String,
      required: true,
    },
    className: {
      // 自定义类名
      type: String,
      default: "",
    },
    color: {
      // 图标颜色
      type: String,
    },
    size: {
      // 图标大小,宽=高
      type: [Number, String],
    },
  },
  computed: {
    iconName() {
      return `#${this.name}`;
    },
    svgStyle() {
      const { color, size } = this;
      const style = {};
      if (color) {
        style.color = color;
      }
      if (size) {
        style.width = `${size}px`;
        style.height = `${size}px`;
      }
      return style;
    },
  },
});
</script>

<style lang="scss">
.svg-icon {
  overflow: hidden;
  display: inline-block;
  width: 1em;
  height: 1em;
  fill: currentColor;
  font-size: 16px;
  vertical-align: middle;
}

svg:not(:defined) {
  fill: currentColor;
}
</style>

# 记录一些细节

  1. 发现从 iconfont 下载的 svg 有 fill 属性时(非 currentColor),无法在外层 css 通过 color 来修改颜色,需要删除掉 fill 属性。