[CSS翻译]在生产中使用内联SVG时,你将面临的5个难题

682 阅读15分钟

原文地址:css-tricks.com/gotchas-on-…

原文作者:css-tricks.com/author/robl…

发布时间:2018年6月19日

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

你已经阅读了内嵌式SVG比字体图标更好的方法,并准备好了投身其中。你召集你的团队开会,讨论转向内联SVG图标的问题。你的老板对此持怀疑态度。他看着你的眼睛说:"那么,你能保证这不会回来咬我们的屁股吗?"。你犹豫了一下,但不知怎的,还是唤起了信心,确认道:"是的,这绝对是我们需要走的方向!"

这就是几个月前的我,下面是我遇到的一些 "疑难杂症 "与相应的解决方法。我先逐一研究一下这些变通方法,然后在最后提供一个工作实例。

请注意,这不是一篇关于为什么你应该使用内联SVG的说服力文章。为此,你应该阅读这篇广受欢迎的CSS-Tricks文章,它指出了内联SVG比图标字体的优势。

疑难杂症一:错过目标

为了实现使用外部SVG文件进行缓存(你真的不想在你的页面上以不可缓存的方式倾倒~1.5kb*50个图标吧?!),你需要在你的页面上包含svg4everybody库。本质上,这个shiv会使用UA嗅探来检测你运行的IE或Android的 "问题版本 "是否不能正确缓存外部定义文件,如果是这样,就会删除所有的svg use元素,并用包含通过ajax拉入的相应SVG定义数据的嵌入式元素来代替它们。到最后,我们只关心我们原来的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针对 "pollyfilled "的情况--这意味着你的CSS规则绝对不应该直接针对SVG > use元素......它将在IE中被完全删除。

JavaScript。锁定目标

同样的想法也适用于对SVG克隆本身进行的任何JavaScript操作。例如,我们可能想在悬停时交换图标,我们可能会选择一种技术,即当这种事件发生时,用JavaScript改变xlink:href属性。因为在本文中,我们选择使用一个带有上述shim的外部文件,所以我们不能可靠地使用这种技术(同样,在IE中use元素会被替换)。我的建议是通过CSS类来隐藏/显示即可(技巧1在Swapping Out SVG Icons一文中描述),并且一定要针对SVG克隆本身。

如果我们直接将SVG的内嵌定义放在页面的顶部(例如,就在开头的<body>标签之后),我们就不会有这样的顾虑,使用操作xlink:href属性的技术就可以了。

选择器示例

为了让上面的观点更加清晰,这里有一个CSS选择器,它可以在完全支持外部SVG定义的浏览器中工作,但是当svg4everybody pollyfills IE时就会失败。

.my-svg use {
  fill: red;
}

原来并没有真正的需求,也没有针对use的收益,所以就改成以下的方式,适用于所有情况。

.my-svg {
  fill: red;
}

当我们在讨论选择器的时候,我们应该借此机会指出,你将无法通过类似以下的方式 "进入 "原始SVG定义。

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

同样的道理也适用于试图通过克隆的实例来为def本身的任何东西设置样式,无论是形状、路径、组等等。这可能是显而易见的,但这只是一个问题,在这里,因为我们使用的是use xlink:href策略。

疑难杂症二:与设计师一起工作

如果你的图标一般只使用一种颜色,那么在 "一扫 "中对克隆的实例应用CSS样式是很简单的,使用:fill: <your-color>。对于这种情况,项目中的设计师需要注意创建矢量艺术应用:只有黑色填充与透明笔触,或者,只有路径数据(透明填充和笔触)。如果你需要的话,你仍然可以通过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被应用,一切都很好。

导出 "笔触开启"/"透明填充 "功能

所以,坏消息是,你不能在克隆的实例上应用样式(记住,我们的克隆实例,在这个例子中,反过来,指向一个我们用 "stroke only "创建的SVG def)。我们也不能给克隆的实例应用填充,因为我们的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;
}

请注意,上面选择器的符号部分是不必要的,但在这里使用是为了明确我们要针对的是什么元素

同样,如果你使用的是内联SVG和克隆实例的方法,这并不是很理想,因为我们更愿意在可能的情况下将样式应用到我们的克隆实例上。

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

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

也许我显示出了我的年龄,但我只是不由自主地想到了Clarence Carter,所有这些关于 "strokin "的谈话。

在我看来,只能够直接对SVG进行样式设计(而不是克隆的实例),是不太理想的。因此,我的建议是,在你要使用内联SVG和克隆实例的情况下,完全避免导出笔画。如果您已经有定义了笔触的作品,您可以在导出之前通过类似 Outline Stroke 的方法将这些笔触转换为路径。

codepen.io/roblevin/em…

后期处理

另一个需要考虑的问题是,如果你使用一个后处理库,比如grunt-svgstore来清理填充和描边。在这种情况下,生成的SVG可能根本没有明确的填充或描边属性,在生成的定义文件中只留下路径信息。对于这种情况,您一定要将任何笔触转换为路径,否则就有可能完全失去相应的可见线。另外,不要要求后处理程序删除笔画(但要面对我前面讨论的一些问题)。一句话,如果你的作品真的必须有笔触,那你就得直接以SVG为目标。

结论

所以我想我的理解是,你需要和设计师一起决定是否要通过CSS来完全控制所有的填充和笔触--在这种情况下,你应该使用带有透明填充和笔触的路径(或者黑色填充,如果你想保留它们作为参考)--或者,是否从合理的默认值开始,然后根据需要使用CSS来覆盖这些默认值更有意义。如果你使用内联SVG,并且喜欢对克隆的实例进行样式设计(而不是直接对SVG进行样式设计),我的建议是直接使用路径。

疑难杂症三。实现颜色变化

一般来说,使用SVG的好处之一是我们可以灵活地控制风格,因为我们可以将CSS应用到SVG的路径、形状等。然而,use xlink:href机制会导致一个非暴露的克隆DOM树,我们的fillstroke样式将适用于全局引用的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 mixin (特意兼容到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抛出一个错误,这个错误在他们的bug跟踪器中有记录。要重现这个错误其实有点棘手,因为你很可能有一个包含块元素作为锚或按钮,而且这个元素会有较大的点击区域,但是,同样,如果你直接点击图标本身,也会发生这种情况。这里是首选的变通方法。

svg { pointer-events: none; }

由于pointer-events继承的,这将导致任何SVG "子元素 "也不响应pointer-events。一旦你设置了这一点,你应该注意确保任何事件处理(比如JavaScriptclick处理)都是由一个祖先元素来处理,而不是由未暴露的SVG克隆本身来处理--按钮或锚是明显的例子,在这种情况下,包装器元素需要做事件处理。

如果你确实打算在SVG本身上处理鼠标或指针事件来制作动画或类似的东西,你可能应该考虑为那个特定的SVG使用一个img或CSS背景标签;这样做会使这个问题不复存在。

第五条。GitHub的差异

我们的一个担心是,SVG diffs对潜在的代码审查员来说是非常过时的。Chris Coyier 向我指出,GitHub 最近部署了一个很好的 svg 查看功能,允许你切换 blob 的视图。非常方便。

GitHub 支持 SVG Diffs!

此外,在整个团队范围内,将这些SVG工作保存在单独的提交中(这样就不会把更有意义的代码修改弄混),可能是一个务实的选择。

一个工作实例

我建立了一个 "玩具示例",希望对你有所帮助。它使用Grunt工作流实现了内联SVG,并依赖于一个可缓存的外部SVG定义文件。请注意,这个例子确实需要IE9及以上版本。

如果你还没有准备好设置这个,这里有一个我们要部署的演示页面

如果你需要支持IE8及以下版本,你可以回退到与你的SVG同名的png图片(但扩展名为.png)。在我们已经使用的svg4everybody库的说明中描述了如何设置,所以从那里开始。

要运行这个例子,你需要安装以下东西。

在你的终端中运行以下命令来获取示例并将其部署到本地。

根据你的系统设置,你可能需要在下面两个npm安装命令之前使用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-line-experiments分支。
  • 安装Buttons节点的依赖性
  • 安装SVG生成器节点依赖性
  • 运行我们的Grunt开发工作流,建立示例的内联SVG定义,同时也建立示例页面。

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

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

如果你想对这个设置进行逆向工程,以便为你自己的项目设置提供参考,你需要参考的文件是:styleguide/includes/svg.html。

  • 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

结论

在成功地解决了以上的挑战之后,我想说的是,建立现代网络应用的团队现在绝对应该考虑使用内联SVG。Unicorn-UI正准备为我们最近发布的Buttons 2.0 Beta版实现一个 "交互式游乐场",由于显而易见的原因,我们肯定会在该项目中使用内联SVG......也许你也应该在你的下一个项目中考虑使用它。


通过www.DeepL.com/Translator (免费版)翻译