使用 SVG 雪碧图的图标系统

Avatar of Chris Coyier
Chris Coyier 发布

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费额度!

我一直是图标字体的坚定支持者。许多网站确实需要一个图标系统,而图标字体提供了一个非常棒的系统。但是,我认为**假设您对 IE 9+ 满意**,使用内联 SVG 和<use> 元素来引用图标是一个更优越的系统。

首先,让我们了解一下它是如何工作的。

处理图标的一种好方法是在一个文件夹中保存一组 .svg 文件。

这是使用 SVG 的一个很酷的功能——它们**就是**源文件。

它们可以是彩色的,也可以是不彩色的,可以有多种形状、大小,等等。

您可以让 Illustrator(或其他软件)以任何方式保存它,包括所有附带的冗余信息。

<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 width="100px" height="100px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve">
<g>
	<path d="M50.049,0.3c14.18,0.332,25.969,5.307,35.366,14.923S99.675,36.9,100,51.409c-0.195,11.445-3.415,21.494-9.658,30.146 - yadda yadda yadda"/>
</g>
</svg>

合并 .svg 文件

如果愿意,您可以手动执行此操作。我做过。您甚至不必查看最终文件。只需将其命名为 svg-defs.svg 或类似名称即可。

它应该只是一个 <svg> 标签,带有一个 <defs> 标签(这仅仅意味着您正在定义一些稍后要使用的内容),然后是一堆 <g>(组)标签。每个 <g> 标签将具有一个唯一的 ID,并且会包装每个图标的所有路径等。

<svg>
  <defs>

    <g id="shape-icon-1">
      <!-- all the paths and shapes and whatnot for this icon -->
    <g>

    <g id="shape-icon-2">
      <!-- all the paths and shapes and whatnot for this icon -->
    <g>

    <!-- etc -->

  </defs>
</svg>
事实证明,<symbol> 可能比 <g> 更好。 阅读所有相关信息!

同样,您可以手动执行此操作,但这有点繁琐。Fabrice Weinberg 创建了一个名为 grunt-svgstore 的 Grunt 插件来自动执行此操作。

如果您从未使用过 Grunt,您可以尝试一下。 这是一个入门教程视频

您可以使用以下命令安装它:

npm install grunt-svgstore --save-dev

使用以下命令确保任务可用:

grunt.loadNpmTasks('grunt-svgstore');

然后在配置中:

svgstore: {
  options: {
    prefix : 'shape-', // This will prefix each <g> ID
  },
  default : {
      files: {
        'dest/svg-defs.svg': ['svgs/*.svg'],
      }
    }
  }
},

在输出文件 svg-defs.svg 中,每个图标(来自源 .svg 文件的任何路径和内容)都将被包装在一个带有唯一前缀 ID 和文件名(不含 .svg)的 <g> 标签中,例如:

<g id="shape-codepen">

在文档顶部注入该 SVG

实际上包含它,例如:

<!DOCTYPE html>
<html lang="en">

<head>
  ...
</head>

<body>
  <?php include_once("processed/svg-defs.svg"); ?>

或者您想如何执行此操作。

不幸的是,它必须位于顶部,因为 Chrome 中存在一个错误,如果稍后定义,此操作将无法正常工作。尽管……这个故事还有更多内容,因为在我键入这些文字时,本网站正在使用的主题在文档底部定义了图标,并且它可以正常工作。Ughkgh 令人困惑。

在任何地方使用图标

现在您可以在任何地方使用它们!例如:

<svg viewBox="0 0 100 100" class="icon shape-codepen">
  <use xlink:href="#shape-codepen"></use>
</svg>
请注意,grunt-svgstore 现在使用的是 <symbol>,因此您甚至不需要使用 viewBox!

确保在 svg 上使用这些类名来调整其大小。

/* Do whatever makes sense here.
   Just know that the svg will be an 
   enormous 100% wide if you don't 
   reign in the width. */
.icon {
  display: inline-block;
  width: 25px;
  height: 25px;
}

太棒了:您可以使用 CSS 样式化它们(及其各个部分)

我们喜欢图标字体的其中一个原因是能够使用 CSS 对其进行样式化。此技术在这方面更胜一筹,因为我们可以在其中执行所有操作,甚至更多,因为

  1. 我们可以样式化所有单独的部分
  2. SVG 还有更多您可以控制的内容,例如特殊滤镜和笔划

svg 位于(某种程度上)DOM 中,因此 JavaScript 也可以。这里有一些样式化可能性以及所有这些工作的演示

查看 Chris Coyier 在 CodePen 上的笔 EBHlD (@chriscoyier)。

另一种方法:IcoMoon

IcoMoon 以生成图标字体而闻名,实际上也能够出色地生成 SVG 雪碧图。选择所有所需的字体后,点击底部的 SVG 按钮,您将获得该输出,包括一个使用内联 SVG 方法的演示页面。

浏览器支持

在浏览器支持方面,危险区域是 IE 8 及更低版本、Safari 5 及更低版本、iOS 4.3 及更低版本以及 Android 2.3 及更低版本。但是,如果您的策略是“最近两个主要版本”,那么您将获得几乎 100% 的支持。

请记住,图标只能用作辅助角色,例如始终伴随文字。如果是这种情况,支持并不是什么大问题。如果这些是独立的,并且不显示会导致网站无法使用,那将是一个大问题。

我可能会选择图标字体,因为那里的支持范围更广。只需确保您 正确地执行此操作

这将变得更好

理想情况下,我们将能够做到这一点:

<svg viewBox="0 0 100 100" class="icon shape-codepen">
  <use xlink:href="/images/svg-defs.svg#shape-codepen"></use>
</svg>

这在某些浏览器中确实有效,这意味着您可以跳过在文档顶部包含该文件。以这种方式执行此操作意味着额外的 HTTP 请求,但这意味着您可以更有效地利用缓存(而不是膨胀文档缓存)。在测试中,Jonathan Neal 发现您需要在 <svg> 上具有 xmlns 属性才能使其工作。

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

但即使这样,在任何 IE 中都不支持。**除非**您想用 <object> 替换整个 <svg><use>,这确实有效。Jonathan Neal 再次 解决了这个问题

/MSIE|Trident/.test(navigator.userAgent) && document.addEventListener('DOMContentLoaded', function () {
  [].forEach.call(document.querySelectorAll('svg'), function (svg) {
    var use = svg.querySelector('use'); 

    if (use) {
      var object = document.createElement('object');
      object.data = use.getAttribute('xlink:href');
      object.className = svg.getAttribute('class');
      svg.parentNode.replaceChild(object, svg);
    }
  });
});

他的演示 现在还有一种方法可以对内容发出 Ajax 请求并注入它,这使得填充可以在 IE 9 中工作。效率不高,但更像是 polyfill。

我想总有一天直接 <svg><use> 链接到 .svg 将成为首选方法。或者甚至可能是 <img> 使用 SVG 上的 URL 片段标识符。


浏览器将 <use> 视为 Shadow DOM

现在,我们可以使用 CSS 针对特定的 <path>,例如:

.targetting-a-path {
  fill: red;
}

但这会影响该路径的所有实例。您可能会认为您可以执行以下操作:

svg.shape-version-2 .targetting-a-path {
  fill: red;
}

但这不起作用。它跨越了 Shadow DOM 边界。理想情况下,您将使用“帽子”选择器来打破它

svg.shape-version-2 ^ .targetting-a-path {
  fill: red;
}

但该功能尚未得到支持,并且尚不清楚这是否正是它的工作方式。

“对比”图标字体

**基于矢量:**平局

**使用 CSS 样式化:**SVG 雪碧图略胜一筹(定位部分,SVG 特定样式,如笔划)

**奇怪的故障:**SVG 似乎可以正常工作(在受支持的情况下)。图标字体似乎会以奇怪的方式失败。例如,您将字符映射到普通字母,然后字体加载失败,您会得到大量随机字符。或者您映射到“专用区域”,某些浏览器会将其重新映射到 非常奇怪的字符,例如玫瑰,但很难复制。或者您想将 @font-face 文件托管在 CDN 上,但那是跨域的,Firefox 不喜欢它,因此您的服务器需要提供正确的跨域标头,但您的 Nginx 设置没有正确捕获它,唉。SVG 在这一点上胜出。

**语义:**这不是什么大问题,但我认为对于图像来说,<svg><span> 更合理。

可访问性:也许有人能告诉我?我们能否/应该为<svg>添加一个标题属性或其他内容?或者在其中添加一个我们视觉上隐藏的<text>元素?更新:<title>元素可能有效。或者像这个SVG 可访问性规范中使用的那样,使用<desc>元素。

易用性:FontelloIcoMoon这样的工具对于图标字体工作流程来说非常不错,但我认为,使用 Grunt 将一文件夹的 SVG 合并在一起的方法甚至更容易。


Ian Feather发布了一篇文章,讲述了他们为什么放弃图标字体,我同意他提出的每一个观点。