以下是 Rob Levin 和 Chris Rumble 的客座文章。Rob 和 Chris 都在 Mavenlink 的产品设计团队工作。Rob 也是 SVG Immersion Podcast 的创建者和主持人,并在 2014 年撰写了最初的 5 个坑 文章。Chris 是一位来自旧金山的 UI 和动效设计师/开发人员。在这篇文章中,他们回顾了在 2 年多前将内联 SVG 集成到 Mavenlink 的旗舰应用程序后遇到的其他一些问题。文章插图由 Rob 完成,并且 – 本着我们主题的精神 – 100% 是矢量 SVG!
哇,自从我们发布 将 SVG 应用于生产环境的 5 个坑 文章已经两年多了。好吧,我们遇到了一些新的坑,是时候发布另一篇后续文章了!我们将这些坑标记为 6-10,向原始文章中的前 5 个坑致敬 :)
坑六:IE 拖放 SVG 消失

如果您查看上面的动画 GIF,您会注意到我在左侧有一个任务图标的下拉列表,我尝试将行拖到可排序容器元素之外,然后,当我将行放回时,SVG 图标完全消失了。这个阴险的错误在我的测试中似乎没有发生在 Windows 7 IE11 上,但在 Windows 10 的 IE11 上发生了!虽然,在我们的示例中,问题是由于使用了 jQuery UI Sortable 和 nestedSortable 插件 的组合导致的(它需要能够将项目拖出容器以实现嵌套,任何类型的 DOM 元素分离和/或在 DOM 中移动它们等都可能导致这种消失行为。奇怪的是,在撰写本文时,我无法找到 Microsoft 的工单,但是,如果您有权访问 Windows 10/IE11 设置,您可以亲眼看到这种情况是如何发生的,请查看这个 简单的笔,它是从 fergaldoyle 分叉而来。该笔显示了相同的消失行为,但是,这次是由于简单地通过 JavaScript 的 appendChild
移动包含 SVG 图标的元素导致的。
解决此问题的办法是在回调被调用时,重置 <use>
元素的 href.baseVal
属性,这些 <use>
元素是从 event.target
容器元素派生的。例如,在使用 Sortable 的情况下,我们能够从 Sortable 的 stop
回调内部调用以下方法
function ie11SortableShim(uiItem) {
function shimUse(i, useElement) {
if (useElement.href && useElement.href.baseVal) {
// this triggers fixing of href for IE
useElement.href.baseVal = useElement.href.baseVal;
}
}
if (isIE11()) {
$(uiItem).find('use').each(shimUse);
}
};
我省略了 isIE11
的实现,因为它可以通过多种方式完成(遗憾的是,最可靠的方法是嗅探 window.navigator.userAgent
字符串并匹配正则表达式)。但是,总体思路是,在容器元素中找到所有 <use>
元素,然后将其 href.baseVal
重新分配给 IE 以重新获取这些外部 xlink:href
。现在,您可能有一整行复杂的嵌套子视图,可能需要采用更蛮力的方法。在我的例子中,我还需要做
$(uiItem).hide().show(0);
重新渲染行。您的里程可能会有所不同;)
如果您在 Sortable 之外遇到此问题,您可能只需要挂接到父/容器元素上的某个“之后”事件,然后执行相同的操作。
由于我对这个 IE11 特定的问题感到困惑,因此如果您自己也遇到过此问题,有任何替代解决方案和/或对 IE 根问题有更深入的了解,请务必发表评论。
坑七:用 Ajax 策略替换 SVG4Everybody 以提升 IE 的性能
在原始文章中,我们建议使用 SVG4Everybody 作为不支持使用外部 SVG 定义文件并在通过 xlink:href
属性引用时进行填充的 IE 版本的垫片。但是,事实证明,这样做会导致性能问题,并且可能更加笨拙,因为它基于用户代理嗅探正则表达式。更“直接”的方法是使用 Ajax 加载 SVG 雪碧图。以下是我们执行此操作的代码片段,它与链接文章中的代码基本相同
loadSprite = null;
(function() {
var loading = false;
return loadSprite = function(path) {
if (loading) {
return;
}
return document.addEventListener('DOMContentLoaded', function(event) {
var xhr;
loading = true;
xhr = new XMLHttpRequest();
xhr.open('GET', path, true);
xhr.responseType = 'document';
xhr.onload = function(event) {
var el, style;
el = xhr.responseXML.documentElement;
style = el.style;
style.display = 'none';
return document.body.insertBefore(el, document.body.childNodes[0]);
};
return xhr.send();
});
};
})();
module.exports = {
loadSprite: loadSprite,
};
这对我们来说有趣的部分是 – 在我们图标密集的页面上 – 我们将 IE11 中的加载时间从大约 15 秒缩短到大约 1-2 秒(对于第一次未缓存的页面访问)。
在使用 Ajax 方法时需要考虑的一件事是,在 HTTP 请求得到解决之前,您可能需要处理“没有 SVG 的闪现”。但是,如果您已经有一个加载缓慢的初始 SPA 风格应用程序,并且会显示一个加载器或进度指示器,那么这可能是一笔沉没成本。或者,您可能希望直接内联您的 SVG 定义/雪碧图并利用缓存来获得更好的感知性能。如果是这样,请衡量您增加了多少负载。
坑八:设计非缩放描边图标
在您想要拥有相同图标的不同尺寸的情况下,您可能希望锁定这些图标的描边大小…
为什么,问题是什么?

假设您有一个 height: 10px; width: 10px;
的图标,其中有一些 1px
的形状,并将其缩放至 15px
。这些 1px
的形状现在将变为 1.5px
,这最终会导致由于边框显示在子像素边界上而导致图标变模糊或不清晰。这种模糊度还取决于您缩放到的尺寸,因为这将影响您的图标是否位于子像素边界上。通常,最好控制图标的清晰度,而不是将其留给查看者的浏览器的处理。
另一个问题更多的是视觉权重问题。当您使用填充缩放标准图标时,它会按比例缩放……我可以听到您说“SVG 应该这样做”。是的,但是能够控制图标的描边可以帮助它们感觉更相关,并且被视为更像一个系列。我喜欢将其比作使用文本字体进行标题设置,而不是使用显示或标题字体,您可以这样做,但为什么不使用紧凑且清晰的 UI 呢?
准备图标
我主要使用 Illustrator 创建图标,但有很多工具都可以正常工作。这只是我在其中一个工具中的工作流程。我开始创建图标时,会专注于它需要传达的信息,而不是任何技术细节。在我对它满足了我的视觉需求感到满意后,我开始缩放和调整它以满足我们的技术需求。首先,将您的图标调整大小并对齐到像素网格(在 Mac 上,在 Illustrator 中使用 ⌘⌥Y 进行像素预览)到您将要使用的尺寸。我尽量将对角线保持在 45°,并调整任何曲线或奇形怪状的形状以防止它们变得怪异。对此没有固定的公式,只需尽可能将其调整到您喜欢的效果即可。如果它在所需的尺寸下无法正常工作,有时我会放弃整个想法并从头开始。如果它是最佳的视觉解决方案,但没有人能识别出来……它就没有什么价值。
导出 AI
我通常只使用 Illustrator 中的“导出为 SVG”选项,我发现它为我提供了一个标准且最小的起点。我使用“演示属性”设置并保存它。它看起来像这样
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<title>icon-task-stroke</title>
<polyline points="5.5 1.5 0.5 1.5 0.5 4.5 0.5 17.5 17.5 17.5 17.5 1.5 12.5 1.5" fill="none" stroke="#b6b6b6" stroke-miterlimit="10"/>
<rect x="5.5" y="0.5" width="7" height="4" fill="none" stroke="#b6b6b6" stroke-miterlimit="10"/>
<line x1="3" y1="4.5" x2="0.5" y2="4.5" fill="none" stroke="#b6b6b6" stroke-miterlimit="10"/>
<line x1="17.5" y1="4.5" x2="15" y2="4.5" fill="none" stroke="#b6b6b6" stroke-miterlimit="10"/>
<polyline points="6 10 8 12 12 8" fill="none" stroke="#ffa800" stroke-miterlimit="10" stroke-width="1"/>
</svg>
我知道您在那里看到了一些 0.5 像素!关于这一点,似乎有几种不同的观点。我更喜欢将描边与像素网格对齐,因为这将是最终显示的效果。坐标放置在 0.5 像素上,以便您的 1px 描边在路径的两侧各为 0.5。它看起来像这样(在 Illustrator 中)

坑九:实现非缩放描边
清理
我们的 Grunt 任务(Rob 在上一篇文章中提到过)几乎清理了所有内容。不幸的是,对于 non-scaling-stroke
,您需要对 SVG 进行一些手动清理,但我保证这很容易!只需在您希望限制笔划缩放的路径上添加一个类。然后,在您的 CSS 中添加一个类并应用属性 vector-effect: non-scaling-stroke;
,它应该看起来像这样
.non-scaling-stroke {
vector-effect: non-scaling-stroke;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18">
<title>icon-task-stroke</title>
<polyline class="non-scaling-stroke" points="5.5 1.5 0.5 1.5 0.5 4.5 0.5 17.5 17.5 17.5 17.5 1.5 12.5 1.5" stroke="#b6b6b6" stroke-miterlimit="10"/>
<rect class="non-scaling-stroke" x="5.5" y="0.5" width="7" height="4" stroke="#b6b6b6" stroke-miterlimit="10"/>
<line class="non-scaling-stroke" x1="3" y1="4.5" x2="0.5" y2="4.5" stroke="#b6b6b6" stroke-miterlimit="10"/>
<line class="non-scaling-stroke" x1="17.5" y1="4.5" x2="15" y2="4.5" stroke="#b6b6b6" stroke-miterlimit="10"/>
<polyline class="non-scaling-stroke" stroke="currentcolor" points="6 10 8 12 12 8" stroke="#ffa800" stroke-miterlimit="10" stroke-width="1"/>
</svg>
这可以防止笔划(如果指定)在 SVG 缩放时发生变化(换句话说,即使 SVG 的整体尺寸发生变化,笔划也将保持 1px)。我们还在 CSS 脚本中的一个类中添加 fill: none;
,在那里我们也控制笔划颜色,因为它们默认情况下将填充 #000000
。就是这样!现在,您拥有了漂亮的像素精确笔划,可以保持笔划宽度!
在所有操作完成后(并且您已根据第一篇文章通过 grunt-svgstore 进行预处理),您的 SVG 在 defs 文件中将如下所示
<svg>
<symbol viewBox="0 0 18 18" id="icon-task-stroke">
<title>icon-task-stroke</title>
<path class="non-scaling-stroke" stroke-miterlimit="10" d="M5.5 1.5h-5v16h17v-16h-5"/>
<path class="non-scaling-stroke" stroke-miterlimit="10" d="M5.5.5h7v4h-7zM3 4.5H.5M17.5 4.5H15"/>
<path class="non-scaling-stroke" stroke="currentColor" stroke-miterlimit="10" d="M6 10l2 2 4-4"/>
</symbol>
</svg>
CodePen 示例
左侧的图标集按比例缩放,右侧我们使用的是 vector-effect: non-scaling-stroke;
。如果您注意到调整大小的 SVG 图标的笔划开始变得失控,上述技术将使您能够锁定这些笔划。
查看 Chris Rumble(@Rumbleish)在 CodePen 上的 Pen SVG 图标:非缩放笔划。
陷阱十:可访问性
在构建 SVG 图标系统时,很容易忽略可访问性。这很可惜,因为 SVG 本身就是可访问的,尤其是在与图标字体相比时,图标字体众所周知并不总是能与屏幕阅读器很好地配合使用。至少,我们需要添加一些代码来防止屏幕阅读器宣布嵌入在 SVG 图标中的任何文本。虽然我们很想只添加一个带有替代文本的 <title>
标签并“就此作罢”,但 Simply Accessible 的人们发现,Firefox 和 NVDA 实际上不会宣布 <title>
文本。
他们的建议是将 aria-hidden="true"
属性应用于 <svg>
本身,然后添加一个具有 .visuallyhidden
类的相邻 span
元素。该视觉隐藏元素的 CSS 将在视觉上隐藏,但其文本将可供屏幕阅读器宣布。我感到很遗憾,因为它感觉不太语义化,但在 <title>
标签(以及 role
、aria-labelledby
等朋友的组合)在浏览器和屏幕阅读器实现中都得到支持之前,这可能是一个合理的折衷方案。在我看来,SVG 上的 aria-hidden
可能是最大的优势,因为我们不希望无意中触发屏幕阅读器,例如页面上的 50 个图标!
以下是借鉴自 Simply Accessible 的 pen 并略作修改的通用模式
<a href="/somewhere/foo.html">
<svg class="icon icon-close" viewBox="0 0 32 32" aria-hidden="true">
<use xlink:href="#icon-close"></use>
</svg>
<span class="visuallyhidden">Close</span>
</a>
如前所述,这里有两个有趣的地方
- 应用
aria-hidden
属性以防止屏幕阅读器宣布嵌入在 SVG 中的任何文本。 - 讨厌但有用的
visuallyhidden
span,它将由屏幕阅读器宣布。
老实说,如果您宁愿只使用 <title>
标签等方法来编写此代码,我不会与您争论,因为它确实感觉很笨拙。但是,正如我向您展示的我们使用的代码一样,您可以将此解决方案视为版本 1 实现,然后在支持更好的情况下轻松地进行切换……
假设您有一些用于生成 use xlink:href
片段的集中式模板助手或实用程序系统,那么实现上述操作非常容易。我们在 Coffeescript 中执行此操作,但由于 JavaScript 更通用,因此以下是解析到的代码
templateHelpers = {
svgIcon: function(iconName, iconClasses, iconAltText) {
var altTextElement = iconAltText ? "" + iconAltText + "" : '';
var titleElement = iconTitle ? "<title>" + iconTitle + "</title>" : '';
iconClasses = iconClasses ? " " + iconClasses : '';
return this.safe.call(this, "<svg aria-hidden='true' class='icon-new " + iconClasses + "'><use xlink:href='#" + iconName + "'>" + titleElement + "</use></svg>" + altTextElement);
},
...
为什么我们将 <title>
标签作为 <use>
的子元素而不是 <svg>
的子元素?根据 Amelia Bellamy-Royds(受邀的 SVG 和 ARIA 规范专家 @w3c。O'Reilly Media 出版社的 SVG 书籍作者)的说法,您将在更多浏览器中获得工具提示。
以下是 .visuallyhidden
的 CSS。如果您想知道我们为什么以这种特殊方式而不是使用 display: none;
或其他熟悉的方法进行操作,请参阅 Chris Coyier 的文章,其中对这一点进行了深入解释
.visuallyhidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
width: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
position: absolute;
}
此代码并非旨在以“复制粘贴”的方式使用,因为您的系统可能会有细微的差异。但是,它展示了通用方法,并且重要的部分是
iconAltText
,它允许调用者提供替代文本(如果合适,例如,图标并非纯粹是装饰性的)。aria-hidden="true"
,现在始终放置在 SVG 元素上。.visuallyhidden
类将视觉上隐藏元素,同时仍使该元素中的文本可供屏幕阅读器使用
如您所见,稍后可以轻松地重构此代码以使用通常推荐的 <title>
方法,并且如果我们选择这样做,至少维护成本不会太高。相关的重构更改可能类似于
var aria = iconAltText ? 'role="img" aria-label="' + iconAltText + '"' : 'aria-hidden="true"';
return this.safe.call(this, "<svg " + aria + " class='icon-new " + iconClasses + "'><use xlink:href='#" + iconName + "'>" + titleElement + "</use></svg>");
因此,在此版本中(感谢 Amelia 提供 aria 部分!),如果调用者传入替代文本,我们不会隐藏 SVG,也不会使用视觉隐藏 span 技术,同时将 role
和 aria-label
属性添加到 SVG。这感觉更干净,但陪审团尚未确定屏幕阅读器是否会像使用视觉隐藏 span 技术一样支持此方法。也许“专家”(Amelia 和 Simply Accessible 的人员)将在评论中发表意见 :)
额外陷阱:使 viewBox 宽度和高度为整数,否则缩放会变得怪异
如果您有一个导出后具有如下 viewBox
的 SVG 图标:viewBox="0 0 100 86.81"
,如果您使用 transform: scale
,可能会遇到问题。例如,如果您通常将宽度和高度设置为相等(例如 16px x 16px),您可能期望 SVG 应该只是将其自身居中在其包含框中,尤其是在使用 preserveAspectRatio
的默认值时。但是,如果您尝试对其进行任何缩放,您将开始注意到裁剪。
在下方的 Adobe Illustrator 屏幕截图中,您可以看到“吸附到网格”和“吸附到像素”都已选中

以下 Pen 显示前三个图标被裁剪。此特定图标(它被定义为 <symbol>
,然后使用我们已经讨论过的 xlink:href
策略进行引用)具有非整数高度为 86.81 的 viewBox,因此我们看到侧面出现了裁剪。接下来的 3 个示例(图标 4-6)具有整数宽度和高度(viewBox 的第三个参数是宽度,第四个参数是高度),并且不会裁剪。
查看 Rob Levin(@roblevin)在 CodePen 上的 Pen SVG 图标:缩放裁剪测试 2。
结论
上述挑战只是我们在 Mavenlink 遇到的一些挑战,我们的应用程序中已经拥有一个全面的 SVG 图标系统超过两年了。鉴于我们碎片化的世界中存在各种浏览器、屏幕阅读器和操作系统,其中一些挑战的神秘本质是意料之中的。但是,也许这些额外的陷阱将帮助您和您的团队更好地强化您的 SVG 图标实现!
感谢此更新。我一直关注着这个网站上关于内联 SVG 的文章。这些信息对未来很有用(特别是关于笔划与填充缩放问题的建议)
IE11:我已经得出结论,如果非常旧的浏览器(将 Evergreen 版本与 4 年以上的 IE11 进行比较)存在一些轻微的界面错误,如果存在此类轻微错误,就让它们存在吧。考虑到 IE11 正在逐渐消失,这并不值得花费时间。
在过去的 20 年里,我的解决方案一直是规划未来,并且只修复过时浏览器的关键错误。这节省了大量时间,避免学习和实现黑客(有人用过星号黑客吗?),然后在不需要时花费未经资助的时间来移除黑客。
如果客户抱怨,只需简单地说“您正在运行一个不符合 Web 标准的浏览器(或一个即使微软也不再支持的非常旧的浏览器)”。每次都能解决问题,除非他们愿意为此付出代价,以避免他们的屏幕上出现小故障。涉及金钱时,他们更容易接受 IE 的限制。
谢谢 Vanderson,很高兴您觉得它有帮助。
我既能理解您作为开发人员的立场,也能理解那些由于系统管理员或公司政策(由于先前购买的软件要求等)而受困于某个 IE 版本的客户。听起来您要么是自由职业者,要么拥有自己的自筹资金产品,在这种情况下,如果您预先向他们提供了“支持的浏览器”信息,那么您的方法是合理的。在我看来,处理 IE 的方法属于“好吧,这取决于”类型的事情 :)
我遇到过很多客户需要使用旧版浏览器。(主要是企业环境。)而且现在这些相同的客户以及更多客户都卡在IE 11上,就像您正在处理的那样。在此之前,IE 6 持续多年不消亡。因此,我只是不使用任何破坏IE 6 的CSS或花哨的功能。
对于任何细微的视觉错误,我都会问他们是否愿意支付额外费用来修复IE中的细微视觉问题,他们总是拒绝并表示无关紧要。每次都是这样。我让客户自己选择。
这包括上面列出的示例,如今IE 11 中的细微视觉问题,明年或更早就会成为非问题,具体取决于客户是否遭到黑客攻击并不得不更换浏览器,或强制更新到Win 10等等。
此外,这些类型的界面几乎从来都不是面向公众的。面向公众的内容应该与IE 11兼容。但这些内容通常只是一个网站,而不是应用程序。然后花哨的界面内容就不会出现或导致此类问题。
Vanderson,我绝对不会将IE11等浏览器与“A级”浏览器保持相同的视觉美学标准,我绝对理解你的感受……我们都经历过这种开发“痛苦”:)
明白了!
但是,通过Jake Archibald的SVGOMG进行处理是否也是一个好主意?他有一些功能应该可以快速进行必要的更改?
此致,
Mic
嗨,Mic,是的,我听说过SVGOMG的好评,并且见过类似我们使用该工具的方法。我相信它可以用来做我们正在做的事情,但是,我只能在免责声明中说明我还没有亲自尝试过。也许,如果您走这条路,可以参考一些列出的注意事项,并弄清楚是否/如何通过该工具获得支持。
当我和Chris Rumble一起撰写这篇文章时,我们意识到目前可能有多种工具/方法可用于处理SVG图标,而且,许多人正在转向各种Webpack和朋友捆绑器,这将需要完全不同的方法。我们撰写本文的目的是针对此特定工具链提供新的发现,因为第一篇文章发表于2014年,而且很多团队(比如我们)可能仍在使用此设置,并且还没有看到转向完全不同方向的投资回报。
也就是说,我差点写了一个注意事项,“如果您的团队正在转向Webpack及其朋友,也许是时候采用不同的方法了”。但是,截至目前,在我获得一些实践经验之前,我还没有办法谈论这个方面。如果有人愿意发布他们成功实施的类似于我们方法的经过实战检验的解决方案的链接,我个人会很乐意,但使用SVGOmg和/或Webpack :)
您知道svgxuse是否也存在与SVG4Everybody相同的性能问题吗?
我还没有用过这个。我确实喜欢它使用特性检测而不是正则表达式,尽管对于IE,我想知道这到底有多可靠。我仍然更喜欢“始终使用ajax加载”的方法,但我可以看到这种方法也存在问题(如文章中所述)。我的直觉告诉我,每次循环遍历每个
<use>
元素并放置功能是导致我们问题的根源(因为我当时已经急于重构为使用ajax方法,所以我没有费心去查找根本原因,但我认为这肯定是一个性能杀手);话虽如此,这个库似乎也确实这样做了。我不喜欢在没有更好证据的情况下提出建议,因此请公平地将我上面所说的理解为一个有根据的猜测或预感;)