Skip to content
SVG 作为 data URI使用
CSS
Branch: master
Clone or download

Latest commit

Fetching latest commit…
Cannot retrieve the latest commit at this time.

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
README.MD modify readme Jan 12, 2017
svgtourl.sass create svgtodateurl Jan 12, 2017

README.MD

探究 dataURI 中使用 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

不久前,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 的方法是有效的:

  • 1.data:mime/type;base64,[actual data]:base64 编码,更适合于二进制数据(PNG,fonts,SVGZ 等等)
  • 2.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

<?xml version="1.0" encoding="utf-8"?>
<!-- Generated by IcoMoon.io -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<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>

svg优化方面,我们可使用 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 来设定图像的尺寸,你还可以去掉widthheight属性让代码更简洁。

<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("")

我们通过 SASS 中实现了这个算法,使整个流程变得非常简单:

@function svg-url($svg){
    @if not str-index($svg,xmlns) {
        $svg: str-replace($svg, '<svg','<svg xmlns="http://www.w3.org/2000/svg"');   
    }           
    $encoded:'';
    $slice: 2000;
    $index: 0;
    $loops: ceil(str-length($svg)/$slice);
    @for $i from 1 through $loops {
        $chunk: str-slice($svg, $index, $index + $slice - 1); 
        $chunk: str-replace($chunk, '%', '%25');
        $chunk: str-replace($chunk, '"', '%22');
        $chunk: str-replace($chunk, "'", '%27');
        $chunk: str-replace($chunk, '&', '%26');
        $chunk: str-replace($chunk, '#', '%23');       
        $chunk: str-replace($chunk, '{', '%7B');
        $chunk: str-replace($chunk, '}', '%7D');         
        $chunk: str-replace($chunk, '<', '%3C');
        $chunk: str-replace($chunk, '>', '%3E');     
        
        $encoded: #{$encoded}#{$chunk};
        $index: $index + $slice; 
    }
    @return url("data:image/svg+xml,#{$encoded}");   
}
            
@mixin background-svg($svg){
    background-image: svg-url($svg);        
}        
            
@function str-replace($string, $search, $replace: '') {
    $index: str-index($string, $search); 
    @return if($index, 
        str-slice($string, 1, $index - 1) + $replace + 
        str-replace(str-slice($string, $index + 
        str-length($search)), $search, $replace), 
        $string); 
}                          

以上就是如何得到能够在 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 以上的浏览器中都能够完美显示。

参考资料:

Optimizing SVGs in data URIs

Probably Don’t Base64 SVG

You can’t perform that action at this time.