5 个你将遇到的将内联 SVG 引入生产环境的“陷阱”

Avatar of Rob Levin
Rob Levin 发布

DigitalOcean 为你旅程的每个阶段提供云产品。从 $200 免费信用额度 开始!

以下内容是由 Rob Levin 撰写的客座文章。Rob 是 Mavenlink 的高级 UI/UX 开发人员,也是 Unicorn UI CSS 按钮库 的合著者。他们的 2.0 版本使用了一个 SVG 图标系统,在这里他分享了一些他在此过程中遇到的问题,以及你如何注意并解决这些问题。此外,Rob 还提供了一个你可以使用的完整系统,包括一个可工作的构建过程和演示。

你已经阅读了有关内联 SVG 比字体图标更好的内容,并准备开始尝试。你与你的团队开会,讨论迁移到内联 SVG 图标。你的老板持怀疑态度。他看着你的眼睛说:“所以,你能保证这不会反过来咬我们吗?”你犹豫了一下,但不知何故鼓起勇气,确定地回答:“是的,这绝对是我们需要前进的方向!”

这只是我几个月前的情况,以下是我遇到的“陷阱”以及相应的解决方法。我将首先逐一检查这些解决方法,然后在最后提供一个可工作的示例。

请注意,这不是一篇说服你应该使用内联 SVG 的文章。为此,你应该阅读这篇文章 流行的 CSS-Tricks 文章,它指出了内联 svg 比图标字体更具优势。

陷阱一:未命中目标

为了使用外部 SVG 文件实现缓存(你真的不想以不可缓存的方式将 ~1.5kb * 50 个图标倾倒在你的页面上,对吧?!),你需要在你的页面上包含 svg4everybody 库。本质上,这个 shiv 将使用 UA 探测来检测你是否正在运行一个不支持正确缓存外部定义文件的 “问题版本” 的 IE 或 Android,如果是的话,它将删除所有 svg use 元素,并用包含相应的 SVG 定义数据的嵌入元素替换它们,这些数据是通过 ajax 获取的。归根结底,我们只关心原始 SVG 的样子,它可能看起来像这样

<svg viewBox="0 0 16 16">
  <use xlink:href="/path/to/svgdef.svg#your-icon" … ></use>
</svg>

将被替换为一个看起来像这样的嵌入元素

<svg viewBox="0 0 16 16">
  <path> ... </path>
</svg>

CSS:命中目标

根据源 SVG,你最终可能会得到一个类似这样的层次结构:svg path(如上所示),或者可能是 svg g,或者可能是组合的组和路径后代 - 但请记住,你需要你的 CSS 来定位“polyfilled”的情况 - 这意味着你的 CSS 规则绝对不应该直接定位 svg > use 元素...它将在 IE 中被完全删除!

JavaScript:命中目标

同样的想法也适用于对 SVG 克隆本身的任何 JavaScript 操作。例如,我们可能想要在悬停时 交换图标,我们可能选择的一种技术是,当这样的事件触发时,用 JavaScript 更改 xlink:href 属性。由于在本文中,我们选择使用具有上述描述的 shiv 的外部文件,因此我们不能可靠地使用这种技术(同样,use 元素在 IE 中被替换)。我的建议是,只使用 CSS 类隐藏/显示(在 交换 SVG 图标 文章中描述的技术 #1),并确保定位 SVG 克隆本身。

如果我们直接在页面顶部(例如,在打开的 <body> 标签之后)插入内联 SVG 定义,我们就不必担心这个问题,并且可以使用操作 xlink:href 属性的技术。

选择器示例

为了让上述要点更加清晰,以下是一个在完全支持外部 SVG 定义的浏览器中有效的 CSS 选择器,但在 svg4everybody 对 IE 进行 polyfill 时失效

.my-svg use {
  fill: red;
}

事实证明,没有必要或好处去定位 use,所以只需将其更改为以下内容即可在所有情况下都能正常工作

.my-svg {
  fill: red;
}

既然我们正在谈论选择器,我们应该借此机会指出,你将无法使用类似以下方法“进入”原始 SVG 定义

svg.parent path.child { /* won't work! */ }

同样的情况也适用于尝试通过克隆实例(无论是形状、路径、组等)对 def 中的任何内容进行样式设置。这可能很明显,但这里只有在我们使用 use xlink:href 策略时才会出现问题。

陷阱二:与设计师合作

如果你的图标通常只使用一种颜色,那么使用 fill: <your-color> 将 CSS 样式应用于克隆实例的“一次性操作”非常简单。对于这种情况,项目中的设计师需要注意以以下两种方式之一创建矢量图:仅使用黑色填充和透明描边,或者仅使用路径数据(透明填充和描边)。如果你需要,仍然可以通过 CSS 应用描边。

要理解为什么会出现这种情况,我们首先需要了解我们的矢量应用程序如何导出 SVG。

我使用 Adobe Illustrator,但如果你使用其他矢量程序,例如 InkscapeSketch 等,你可能需要参考该软件的文档,看看它们的运行方式是否与以下内容一致。最糟糕的情况是你只需按照以下描述的各种方式导出文件,并测试你的应用程序生成的 SVG 看起来像什么。

Illustrator SVG 导出行为

在撰写本文时,最新版本的 Illustrator CC 将 SVG 导出如下

  • 如果你没有定义填充或描边,或者你只定义了填充,但该填充是黑色(完全黑色,如 #000),那么导出的 SVG 路径和形状将包含填充或描边属性
<path ... positional information ... >
<rect ... positional information ... >
  • 如果你定义了非黑色的填充,或者如果你定义了描边(任何颜色,包括黑色),那么导出的 SVG 的相应路径和形状将包含描边和/或填充属性,例如
<path stroke="#000000" ... >
<rect fill="fabdad" ... >

如果你直接对 SVG 符号、路径、形状等应用样式,这些表示属性始终可以被 CSS 覆盖。在下一节中将描述与将 CSS 应用于克隆实例(而不是直接应用)相关的注意事项。

导出黑色填充和透明描边

如果你的源 SVG 仅使用黑色填充(没有描边)或仅使用路径进行导出,那么你将拥有很大的灵活性,因为你可以通过 CSS 应用描边或填充。

在这个第一个示例中(请注意我使用的是 SCSS 语法),我们向克隆实例应用了 CSS 填充和描边

.filled-instance {
  stroke: #cc8ac1;
  stroke-width: 5px;
  fill: lighten(#cc8ac1, 20%);
}

你还可以通过简单地关闭填充并通过 CSS 应用描边来实现轮廓效果

.filled-instance-off {
  stroke: #d08aaf;
  stroke-width: 5px;
  fill: transparent;
}

之所以能够如此完美地实现,是因为我们的 SVG 定义没有定义任何填充或描边属性,因此我们的 CSS 会被应用,一切正常。

导出透明填充和描边

这里的不利消息是,你无法将样式应用于克隆实例的描边(请记住,在这个例子中,我们的克隆实例指向我们用“仅描边”创建的 SVG 定义)。我们也不能将填充应用于我们的克隆实例,因为我们的 SVG 形状现在有 `fill="none"`,这将具有优先权。)

.stroked-instance {
  stroke: green; /* nothing happens */
  fill: red; /* nothing happens */
}

这与 HTML/CSS 类似,比如

<div id="very-strong">
  <span>still green.</span>
</div>
#very-strong { color: red; }
span { color: green; } /* I'm the actual element, so I win. */

一些解决方法

你可能应该完全避免这种情况,通过导出具有透明描边的黑色填充,或者只是路径,但是,如果你由于某种原因仍然需要你的样式生效,你将不得不直接用类似于以下内容的样式来设置 SVG 符号:

symbol#completed-copy-stroked [stroke] {
  stroke: #dd6435;
  stroke-width: 5px;
}

请注意,上面的选择器中的 `symbol` 部分是不必要的,但在这里使用是为了清楚地说明我们所针对的元素。

同样,如果你正在使用内联 SVG 和克隆实例的方法,这不是理想的,因为我们希望尽可能地将样式应用于克隆实例。

你始终可以使用另一种技术,就是直接在源 SVG 中添加类,并直接将 CSS 应用于这些类。这些样式将是全局的,这可能是一个问题,也可能不是问题——你必须根据自己的情况决定。无论你如何导出 SVG,这种技术都有效。因为你正在手动添加 CSS 类,所以你可以使用它(尽管是直接的——我们直接针对 SVG 定义的子元素,而不是通过克隆实例进行样式化)。

.ibestrokin {
  stroke: magenta;
  stroke-width: 5px;
}
.istrokeittotheeast {
  stroke: green;
  stroke-width: 7px;
}

也许我暴露了我的年龄,但我忍不住想到 Clarence Carter,所有关于 “strokin” 的谈话。

在我看来,只能够直接设置 SVG 样式(而不是克隆实例),并不理想。因此,我的建议是,在使用内联 SVG 和克隆实例的情况下,避免完全导出描边。如果你已经有定义了描边的艺术作品,你可以使用类似于“轮廓描边”之类的工具将它们转换为路径,然后再导出。

查看 CodePen 上 Rob Levin (@roblevin) 的笔 内联 SVG 填充和描边

后期处理

另一个需要考虑的问题是,如果你使用的是 grunt-svgstore 等后期处理库来清理填充和描边。在这种情况下,生成的 SVG 可能根本没有显式的填充或描边属性,并且只有路径信息将保留在生成的定义文件中。对于这种情况,你绝对需要将所有描边转换为路径,否则可能会完全丢失相应的可见线条。或者,不要要求后处理器删除描边(但要面对我之前讨论过的一些问题)。底线是,如果你的艺术作品必须有描边,那么你将不得不直接针对 SVG 进行设置。

结论

因此,我认为关键在于,你需要与设计师一起决定,你是希望通过 CSS 完全控制所有填充和描边——在这种情况下,你应该只使用具有透明填充和描边的路径(或者如果你希望保留填充用于参考,则使用黑色填充)——还是更合理的方式是从合理的默认值开始,然后使用 CSS 根据需要覆盖这些默认值。如果你使用内联 SVG,并且希望对克隆实例进行样式设置(而不是直接对 SVG 进行样式设置),我的建议是只使用路径。

问题三:实现颜色变化

一般来说,使用 SVG 的一个好处是可以灵活地控制样式,因为我们可以将 CSS 应用于 SVG 的 `path`、`shape` 等。但是,使用 `use xlink:href` 机制会导致一个 未公开的克隆 DOM 树,我们的 `fill` 或 `stroke` 样式将全局应用于引用的 SVG。这意味着所有克隆实例将共享相同的填充颜色。

幸运的是,我们有一个技巧,至少可以为每个实例获得一种独特的颜色。如果我们进入 SVG 定义本身,我们可以将 `fill=“currentColor”` 应用于我们选择的形状或路径。这有什么作用呢?好吧,长期支持的 CSS 值 `currentColor` 指定颜色将被继承。这意味着我们可以在树中更高的地方定义字体颜色(例如,在克隆实例本身),并且该路径或形状的填充将继承颜色。我不确定是谁第一个想到这个方法的,但我将把功劳归功于我第一次看到它的地方:Jenna Smith 的推文

实现

我们从基础填充开始,它可能看起来像这样

.icon-primary {
  fill: #ccc;
  color: #3bafda;
}

这些类将被放置在未公开的 `svg` 克隆实例上。

<svg class="icon-primary"> ...

现在,神奇的事情开始发生了——我们的 `fill` 定义了图标的常规填充颜色(在本例中为 `#cc`),但现在我们的字体 `color` 定义了我们上面描述的继承的强调颜色,如下所示:

<path fill="currentColor" ... />
使用 `currentColor` 来实现 SVG 路径上的强调颜色的示例

如果你在构建过程中使用 grunt-svgstore(本文末尾的示例使用它),你可能会将其配置为通过 `cleanup` 属性删除不需要的杂物,并且这个库现在 保留具有 `currentColor` 值的填充属性……所以你不用担心破坏上面定义的自定义属性。

我创建了一个 Sass 混合宏(故意兼容到 3.2 版本)来实现这个功能

@mixin svgColors($fill: false, $color: false, $patchCurrentColorForIE: false) {
  @if $fill {
    fill: $fill;
  }
  @if $color {
    color: $color;
  }
}

我使用类似以下内容来调用它

.icon-primary {
  @include svgColors($neutralColor, $primaryColor, true);
}

如果你想知道 `$patchCurrentColorForIE` 参数的作用,我将其添加到不需要多种颜色的图标中,因此不需要应用 shim。

其他颜色变化技术

除了使用上面列出的 `currentColor` 技术之外,你还可以使用 grunt-svgstore 中现在可用的 `preserve--` 属性功能。它的工作原理是,如果你使用 `preserve--` 作为源 SVG 中任何有效属性的前缀,该属性将被强制保留在生成的 SVG 中(`preserve--` 前缀将被删除)。例如,`preserve--stroke` 将在输出 SVG 定义中只生成 `stroke`。

如果你想知道如何实现颜色变化,特别是对于 `background-image` 来说,另一种需要考虑的技术是使用 SVG 的 `data-uri`,但首先要对 `fill` 值进行搜索和替换,就像 这里描述的那样。但是,这种方法有点超出本文的范围,并且偏离了主题,因为它意味着使用不可缓存的 `data-uri`,违背了我们使用可缓存的外部 SVG 定义文件的首要目标。

问题四:jQuery 抛出错误

如果你在页面上包含了 jQuery,直接点击渲染的 `svg use` 元素可能会导致 jQuery 抛出一个错误,该错误在他们的 错误追踪器 中有记录。实际上,这个错误有点难重现,因为你很可能会有一个包含块元素作为锚点或按钮——并且该元素将具有更大的点击区域——但是,如果你直接点击图标本身,同样会发生。以下是推荐的解决方法

svg { pointer-events: none; }

由于 `pointer-events` 是继承的,这将导致任何 SVG “子元素”也不响应 `pointer-events`。设置好之后,你应该注意确保任何事件处理(例如 JavaScript 的 `click` 处理程序)都是由祖先元素处理的,而不是由未公开的 SVG 克隆本身处理的——按钮或锚点是明显需要在这种情况下进行事件处理的包装元素示例。

如果你确实打算在 SVG 本身处理鼠标或指针事件(用于动画等),你可能应该考虑只使用 `img` 或 CSS `background` 标签来处理该特定 SVG;这样做将使这个问题不再存在。

问题五:GitHub 差异

我们当时的一个担心是,SVG 差异对于潜在的代码审阅者来说可能过于陈旧。Chris Coyier 指出,GitHub 最近发布了一个很棒的 SVG 查看功能,它允许您切换对Blob 的查看。非常方便。

GitHub 支持 SVG 差异!

此外,将此类 SVG 工作保留在单独的提交中(这样它不会混淆更有意义的代码更改)的团队范围策略可能是务实的选择。

一个工作示例

我已经设置了一个“玩具示例”,希望您会发现它很有用。它使用 Grunt 工作流程实现内联 SVG,并依赖于可缓存的外部 SVG 定义文件。请注意,此示例需要 IE9 及更高版本。

如果您还没有准备好进行设置,这是一个演示页面,展示了我们将要部署的内容。

如果您需要支持 IE8 及更低版本,您可以回退到与您的 SVG 同名的 png 图像(但扩展名为 .png)。设置方法在 svg4everybody 库的说明中进行了描述,我们已经在使用它了,因此请从那里开始。

要运行示例,您需要安装以下内容

在您的终端中运行以下命令以获取示例并在本地部署它

根据您的系统设置,您可能需要在以下两个 `npm install` 命令之前使用 `sudo`

# Install the Buttons example and set up npm dependencies
git clone -b svg-inline-experiments --single-branch https://github.com/unicorn-ui/Buttons.git Buttons && cd Buttons && npm install

# Install the SVG specific dependencies and run example
pushd svg-builder && npm install && grunt && popd && grunt dev

使用这些命令,我们

  • 克隆存储库,仅获取相关的 `svg-inline-experiments` 分支
  • 安装按钮节点依赖项
  • 安装 SVG 生成器节点依赖项
  • 运行我们的 Grunt 开发工作流程,该工作流程会构建示例的内联 SVG 定义,还会构建示例页面

根据您的设置,最后一步应该会在您的系统默认浏览器中打开以下页面(如果没有,请手动访问 http://localhost:8000)

SVG 图标预览 - 请注意,我们有两个实例指向同一个 SVG 定义,但具有不同的强调颜色

如果您想对这种设置进行逆向工程以告知您自己的项目设置,您需要参考的文件是

  • styleguide/includes/svg.html 显示用于创建 SVG 实例的标记
  • styleguide/scss/module/_svgs.scss 显示应用于我们的 SVG 图标的 CSS 样式
  • svg-builder/Gruntfile.js 专为内联 SVG 定制的工作 Grunt 配置
  • styleguide/pages/index.html 您需要在这个文件中注意的唯一一件事是,我们包含了 svg4everybody.min.js

结论

在成功解决上述挑战之后,我断言构建现代 Web 应用程序的团队现在应该认真考虑使用内联 SVG。 Unicorn-UI 正在为我们最近发布的 Buttons 2.0 Beta 实现一个“交互式游乐场”,出于显而易见的原因,我们肯定会利用内联 SVG 来完成该项目……也许您也应该在您的下一个项目中考虑它。