sass-svg 一个内联 SVG 的 SASS 库

avatar
全栈开发工程师 @京东

一、CSS内联 SVG

一直觉得 内联 SVG 是 HTML 的延伸,但是内联 SVG 往往会造成HTML结构的臃肿。于是笔者尝试着把 SVG 移植到 CSS 中,如下:

background: url(

  

)

目前(2017.01.12)而言,没有任何浏览器的 CSS 支持内联 SVG 代码,所以上述的代码是一段非法的 css。尽管任何浏览器的 CSS 都不支持内联 SVG 代码,但是所有的现代浏览都是支持内联 SVG 文件,如下:

background: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3NTAgNDAwIj4gCQkJPHBhdGggZD0iTTAsMCBMNzUwLDAgTDc1MCw0MDAgTDM4OCw0MDAgTDM3NSwzODcsIEwzNjIsNDAwIEwwLDQwMFoiIGZpbGw9IiMwZjAiPjwvcGF0aD4gCQk8L3N2Zz4=);

内联 SVG 文件即 Data-URI。

二、Data-URI 简介

A new URL scheme, “data”, is defined. It allows inclusion of small data items as “immediate” data, as if it had been included externally.
摘录自:tools.ietf.org/html/rfc239…

简单地说,Data-URI 是一种将小文件转换成直接字面数据的方案(scheme)。Data-URI 的语法如下:

data:[<media type>][;base64],<data>

<media type>: 即指定嵌入数据的 MIME,对于 PNG 的图片,其格式是:image/png,如果没有指定,默认是:text/plain;
[;base64]: base64 编码扩展,非必选项。如果没指定 base64 编码扩展,后面的 <data> 将使用 URL编码(即百分号编码)。
<data>: 编码后的小文件数据。图片文件则是对二进制文件流进行编码,文本文件则是对文本进行编码。

附注:Data-URI 是正式术语,日常中更常用的名字是 Base64。

三、sass-svg

sass-svg 是一个将 SVG 代码转化为 Data-URI 的 Sass 库
了解 Data-URI 后,sass-svg 的作用其实就是把 SVG 进行编码的过程。

1. URL编码

JS 中可以使用 encodeURIencodeURIComponent 来对字符进行 URL 编码,而 Sass 并没有相对就的编码方法。
了解一下 URL编码的知识点:

  • URL 编码的原理:ASCII 字符 = % + 两位 ASCII 码(十六进制)。例如,字符 a 对应的 ASCII 码为 0x61,那么 URL 编码后得到 %61
  • rfc3986」规定 URL 只允许英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及保留字符(! * ' ( ) ; : @ & = + $ , / ? # [ ])。

尽管 Sass 不提供获取 ASCII 码的函数,但创建一张 ASCII 字符 与 ASCII 码 的对照表(map) 却是廉价的。标准的 ASCII 字符一共是 128 个,剔除 rfc3986 规定的不需要编码的字符(84个),再把扣除一些控制符,实际上需要编码的字符只有 13 个,如下:

// ascii 与 16进制对照表
$asciiHexMap: (
    "   ": "%09",
    " ": "%20",
    "\"": "%22",
    "%": "%25",
    "/": "%2f",
    "<": "%3c",
    ">": "%3e",
    "\\": "%5c",
    "^": "%5e",
    "`": "%60",
    "{": "%7b",
    "|": "%7c",
    "}": "%7d"
);

实现 URL 编码要求 sass 版本3.3以上,因为需要用到三个内置函数:str_indexstr_slicestr_insert。 实现 URL 编码的过程不详述,笔者已经将相关代码放到 GIT 仓库,有兴趣的同学可以访问 「sass-svg」。

注意:本文所描述的编码仅针对 US-ASCII 字符集。中文或其它字符不讨论

2. Base64 编码

Base64 使用 US-ASCII 子集的64个字符,即大小写的26个英文字母,0~9,+,/。如下:

'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'

Base64 编码总是基于3个字符,每个字符用8位二进制表示,因此一共24位,再分为4四组,每组6位表示一个 Base64 的值。如果不足3个字符,仍将每个字符对应的二进制串起,再按每组6位表示一个 Base64的值,不足6位的用 0补全,最后生成的 Base64 码不足4位用=补全。

举个例子来感受一下:

me的编码过程

与 URL编码相似,SASS 无法直接获取 ASCII 字符对应的二进制,也无法将6位二进制转成 Base64 码。这意味着需要手动创建两张对照表(map)。如下:

// ascii字符 与 8位二进制 对照表
$asciiMap: (
    "   ": "00001001", " ": "00100000", "!": "00100001", "\"": "00100010", 
    "#": "00100011", "$": "00100100", "%": "00100101", "&": "00100110",
    "'": "00100111", "(": "00101000", ")": "00101001", "*": "00101010", 
    "+": "00101011", ",": "00101100", "-": "00101101", ".": "00101110",
    "/": "00101111", "0": "00110000", "1": "00110001", "2": "00110010", 
    "3": "00110011", "4": "00110100", "5": "00110101", "6": "00110110", 
    "7": "00110111", "8": "00111000", "9": "00111001", ":": "00111010", 
    ";": "00111011", "<": "00111100", "=": "00111101", ">": "00111110",
    "?": "00111111", "@": "01000000", "A": "01000001", "B": "01000010", 
    "C": "01000011", "D": "01000100", "E": "01000101", "F": "01000110",
    "G": "01000111", "H": "01001000", "I": "01001001", "J": "01001010", 
    "K": "01001011", "L": "01001100", "M": "01001101", "N": "01001110",
    "O": "01001111", "P": "01010000", "Q": "01010001", "R": "01010010", 
    "S": "01010011", "T": "01010100", "U": "01010101", "V": "01010110",
    "W": "01010111", "X": "01011000", "Y": "01011001", "Z": "01011010", 
    "[": "01011011", "\\": "01011100", "]": "01011101", "^": "01011110",
    "_": "01011111", "`": "01100000", "a": "01100001", "b": "01100010", 
    "c": "01100011", "d": "01100100", "e": "01100101", "f": "01100110",
    "g": "01100111", "h": "01101000", "i": "01101001", "j": "01101010", 
    "k": "01101011", "l": "01101100", "m": "01101101", "n": "01101110",
    "o": "01101111", "p": "01110000", "q": "01110001", "r": "01110010", 
    "s": "01110011", "t": "01110100", "u": "01110101", "v": "01110110",
    "w": "01110111", "x": "01111000", "y": "01111001", "z": "01111010", 
    "{": "01111011", "|": "01111100", "}": "01111101", "~": "01111110"
);
// 二进制 与 base64 对照表
$base64map: (
    "000000": 'A', "000001": 'B', "000010": 'C', "000011": 'D', 
    "000100": 'E', "000101": 'F', "000110": 'G', "000111": 'H',
    "001000": 'I', "001001": 'J', "001010": 'K', "001011": 'L', 
    "001100": 'M', "001101": 'N', "001110": 'O', "001111": 'P',
    "010000": 'Q', "010001": 'R', "010010": 'S', "010011": 'T', 
    "010100": 'U', "010101": 'V', "010110": 'W', "010111": 'X',
    "011000": 'Y', "011001": 'Z', "011010": 'a', "011011": 'b', 
    "011100": 'c', "011101": 'd', "011110": 'e', "011111": 'f',
    "100000": 'g', "100001": 'h', "100010": 'i', "100011": 'j', 
    "100100": 'k', "100101": 'l', "100110": 'm', "100111": 'n',
    "101000": 'o', "101001": 'p', "101010": 'q', "101011": 'r', 
    "101100": 's', "101101": 't', "101110": 'u', "101111": 'v',
    "110000": 'w', "110001": 'x', "110010": 'y', "110011": 'z', 
    "110100": '0', "110101": '1', "110110": '2', "110111": '3',
    "111000": '4', "111001": '5', "111010": '6', "111011": '7', 
    "111100": '8', "111101": '9', "111110": '+', "111111": '/',
    "======": '=' // base64占位符
);

实现过程不详述。有兴趣可以访问「sass-svg」。

3. sass-svg 的一个实例

开头的非法 CSS,通过 sass-svg 可以改写成:

// scss
background: url(
 sass-svg-base64('
  
 ')
)

通过编译最终会生成:

background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOTMuOTY5IDI4LjIxOSI+IAkJICA8cGF0aCBkPSJNMC45NjksMTEuMzEzIEMtMjAuMjA3LDY5LjcxMSAzMTQuMTgzLC00Ny45ODQgMzkzLjk2OSwyNC4zMTIgIi8+IAkJPC9zdmc+");

结语

感谢阅读本文。关于 sass-svg 的应用,笔者会有后续文章介绍,如果喜欢本文请关注一下我们『凹凸实验室』的微信公众号:AOTULabs


参考资料

svg sass base64 Data-URI Web开发 感谢您的阅读,本文由 凹凸实验室 版权所有。如若转载,请注明出处:凹凸实验室(aotu.io/notes/2017/…