【译】在 dataURI 中使用 SVG 的最佳方法
我搭新版官网的开发过程中,设计师提出一个需求,官网首页的头部要是一个梯形。
我们通过给头部增加一个与背景同色的 SVG 遮罩,来实现这个效果。为了减少首页的请求数量,我们按照以往的思路,直接将 SVG 转换为 base64 后插入了 CSS 文件中。当时的代码是这样的:
.svg {
background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjUwOCIgaGVpZ2h0PSIyNTIuNyIgdmlld0JveD0iMCAwIDI1MDggMjUyLjciPjxwb2x5Z29uIHBvaW50cz0iNCwyNTIuNyAyNTA0LDAgMjUwNCwyNTIuNyIgc3R5bGU9ImZpbGw6I2U2ZWJlYSIgLz48L3N2Zz4=');
}
初步开发完成后,老大要求进一步优化代码,在查询资料时读到了这篇文章:Optimizing SVGs in data URIs。参考文章内容进行优化之后:
.svg {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' version='1.1'%3E%3Cpolygon points='4,252.7 2504,0 2504,252.7' style='fill:%23e6ebea'/%3E%3C/svg%3E");
}
对比这两段代码,最明显的效果是代码量减少了一半多,而且代码非常清晰,几乎就是 SVG 代码原文,日后如果有一些细微的需求变更,比如更改填充色,可以直接在代码里修改无需进行编码转换。
具体细节请看译文:
在 dataURI 中使用 SVG 的最佳方法 (Optimizing SVGs in data URIs)
原文链接:http://codepen.io/Tigt/post/optimizing-svgs-in-data-uris
不久前,CSS-Tricks 发表了 "Probably Don't Base64 SVG",得出结论:如果你在 data URI 中直接使用 SVG,数据量会比转化成 base64 编码格式时小。
这个观点是正确的,但是这里还有一些复杂的地方以及可优化的空间。
更好的浏览器兼容性
例如下面这段代码:
.bg {
background: url('data:image/svg+xml;utf8,<svg ...> ... </svg>');
}
在那些流行于 web 开发者中的浏览器中是有效的,但是在 IE 中则无法正常工作。因为从技术角度来说这是一种畸形的 data URI,而 IE 很严格(原文: This is because technically it's a malformed data URI, and IE is being strict.)。
RFC 2397 定义了 data URI:
URL 的形式:
data:[<mediatype>][;base64],<data>
<mediatype>
描述数据的 MIME 类型,;base64
的出现意味着数据被编码成base64
格式。如果没有声明;base64
,对于 URL 安全字符使用 ASCII 编码,而安全范围以外的字符则使用十六进制数编码为%xx
格式。如果省略<mediatype>
,默认为text/plain;charset=US-ASCII
。
换句话说,根据标准,只有如下两种编码 data URI 的方法是有效的:
data:mime/type;base64,[actual data]
:base64 编码,更适合于二进制数据(PNG,fonts,SVGZ 等等)data:mime/type;charset=[charset],[actual data]
:URL 编码的普通文本,更适合与文本标记语言(SVG,HTML等)
所以,把一个 SVG 文件编码为 data URL 的正确方式为 data:image/svg+xml;charset=utf8,[actual data]
。我猜大部分浏览器对是否存在charset=
字符串比较宽容,但是在 IE 浏览器里是必须的。为了代码的最大兼容性(例如一些小众浏览器,邮件客户端,等等),它应该被包含在内。
但这并不是全部。记得这段话么?
如果没有声明
;base64
,对于 URL 安全字符使用 ASCII 编码,而安全范围以外的字符则使用十六进制数编码为%xx
格式。如果省略<mediatype>
,默认为text/plain;charset=US-ASCII
。
这段话指明,要么进行 base64 编码,或者使用%
格式编码非 URL 安全字符。“URL 安全字符”有一点难以理解,所以我将举一些例子。我将会使用 Chris 在他的测试中使用过的例子:
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" viewBox="0 0 512 512"><g id="icomoon-ignore">
</g>
<path d="M224 387.814v124.186l-192-192 192-192v126.912c223.375 5.24 213.794-151.896 156.931-254.912 140.355 151.707 110.55 394.785-156.931 387.814z"></path>
</svg>
根据 Chris 的建议,我们使用 SVGO 来优化我们的 SVG 文件(如果你更习惯图形界面,GUI 版本:SCGOMG)。结果是这样的:
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><path d="M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.93 0 521.287 151.707 491.48 394.785 224 387.814z"/></svg>
文件小了很多!而且如果你打算用 CSS 来设定图像的尺寸,你还可以去掉 width
和 height
属性让代码更简洁。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.93 0 521.287 151.707 491.48 394.785 224 387.814z"/></svg>
现在,我们把精简后的 SVG 丢进URL 编码器,会得到这样的东西:
%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20d%3D%22M224%20387.814V512L32%20320l192-192v126.912C447.375%20260.152%20437.794%20103.016%20380.93%200%20521.287%20151.707%20491.48%20394.785%20224%20387.814z%22%2F%3E%3C%2Fsvg%3E
目前这是唯一能在 IE 中工作的版本。非常明显,这甚至比 base64 编码过后的 SVG 都要长:
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMjI0IDM4Ny44MTRWNTEyTDMyIDMyMGwxOTItMTkydjEyNi45MTJDNDQ3LjM3NSAyNjAuMTUyIDQzNy43OTQgMTAzLjAxNiAzODAuOTMgMCA1MjEuMjg3IDE1MS43MDcgNDkxLjQ4IDM5NC43ODUgMjI0IDM4Ny44MTR6Ii8
“引号是关键”
你可能注意到了,Chris 使用单引号('
)来界定 data URIs。这是因为他的 SVG 文件未编码时使用双引号("
)来包裹属性值,为了避免冲突而使用了单引号来代替。这一点点微小的改变其实是真正精简 data URI 的关键。
"
和 '
都是有效的属性分隔符(即:attribute="value"
和 attribute='value'
都有效),但是只有 '
可以直接在 URL 中使用而无须编码转换。现在我们替换双引号,编码 <
和 >
,得到:
%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M224 387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.93 0 521.287 151.707 491.48 394.785 224 387.814z'/%3E%3C/svg%3E
所以,当你把 SVG 作为 data URI使用时:
- 用单引号替换包裹属性值的双引号
- 编码
<
,>
,#
,和剩余的"
(例如在文本内容中的双引号),以及其他一直的不安全 URL 字符(例如%
) - 使用双引号来分隔 data URI(
<img src="">
,url("")
)
网友 jakob-e 在 SASS 中实现了这个算法,使整个流程变得非常简单:
以上就是如何得到能够在 IE (以及标准)中使用最精简的 data URI。总结一下:
base64 编码
data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MTIgNTEyIj48cGF0aCBkPSJNMjI0IDM4Ny44MTRWNTEyTDMyIDMyMGwxOTItMTkydjEyNi45MTJDNDQ3LjM3NSAyNjAuMTUyIDQzNy43OTQgMTAzLjAxNiAzODAuOTMgMCA1MjEuMjg3IDE1MS43MDcgNDkxLjQ4IDM5NC43ODUgMjI0IDM4Ny44MTR6Ii8+PC9zdmc+
完全 URL 编码
data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%3E%3Cpath%20d%3D%22M224%20387.814V512L32%20320l192-192v126.912C447.375%20260.152%20437.794%20103.016%20380.93%200%20521.287%20151.707%20491.48%20394.785%20224%20387.814z%22%2F%3E%3C%2Fsvg%3E
最大程度优化的 URL 编码
data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M224%20387.814V512L32 320l192-192v126.912C447.375 260.152 437.794 103.016 380.93 0 521.287 151.707 491.48 394.785 224 387.814z'/%3E%3C/svg%3E
我使用下面这段代码测试跨浏览器兼容性,结果它在 IE9+ 以及 安卓3.x 以上的浏览器中都能够完美显示。