Feb 12

我们可以在代码里插入图片但是不用base64嘛?

Lrdcq , 2019/02/12 02:02 , 程序 , 閱讀(2340) , Via 本站原創
在现代前端网页开发中,为了避免零散的资源加载,比如小图标,重复背景之类的,我们会在html/css/js里将图片转成base64编码直接插入到相关代码中来提升性能与整体可用性。特别是在混编和ssr的场景更常见。但是base64编码的图片问题也很明显——数据量恒定大33%,如果咱们的应用是高流量应用在关注高可用性的同时,流量也极限追求的话,肯定就在想,原理上我们有直接把图片二进制数据直接插入到代码中的方法么?

尝试在dom中直接插入

一般图片的二进制数据,如果不是有特殊的payload信息字符串的话,一般看起来都是乱糟糟的,编码为字符串的时候,不可避免的会碰到括号,逗号之类的,因此直接插入css或者js段落中八成是不行了。那插入dom中呢?经过尝试后这样是可以的:

點擊在新視窗中瀏覽此圖片

大体做法是新建了一个script标签(其他标签的话还要特意做隐藏,比较麻烦),并且把type设置为image/png(正好script有mime通用属性,并且特殊设置以后浏览器就不会自动运行标签内容,也不会报错了)。

然后,尝试通过读取标签的内容并且渲染出来。大体流程是读取标签的innerHTML创建为blob,通过FileReader的readAsDataURL读取的到url:
let img_dom = document.getElementById("img_car");
let img_str = img_dom.innerHTML;

let blob = new Blob([img_str], {type : img_dom.type});

let reader = new FileReader();
reader.onload = function (e) {
  document.getElementById("img_placeholder").src = e.target.result;
}
reader.readAsDataURL(blob);

当然,实践告诉我们,这种方法不行。得到的base64url和原始数据根本对不上。
那么,尝试把字符串拆解为Uint16Array再生成blob呢:
//接上
let data_buf = new Uint16Array(img_str.length);
Array.prototype.forEach.call(data_buf, function (el, idx, arr) {
  arr[idx] = img_str.codePointAt(idx);
});
let blob = new Blob([data_buf], {type : img_dom.type});
//接下

还是不行,和上面的方式运行结果一致,并且在codePointAt的过程中断点,很明显的发现了问题:比如这个png的png头部分0x89504E47,我们的0x89拿到了居然是0x2030,开场就不对了。那么其实,应该是img_dom.innerHTML取得的东西就和dom中的原始数据不一致了。

关注下domAPI的定义,一下子就明白了:绝大部分js中的API甩出来的字符串是DOMString。看定义:DOMString is a UTF-16 String。显然,二进制数据段中不可解释为字符的部分,自然就发生变化了(就像UTF8转换兜底会出现0xEFBFBD那样)。

因此确定,直接在dom元素中塞二进制数据是走不通的,包括js和css,毕竟他们的原始储存方式都是DOMString,无法读取出非DOMString的东西。那么,现代的技术框架中,有没有储存层就是储存为二进制形式的前端(非资源)元素呢。还真有——WebAssembly脚本就是以二进制形式发布的。

尝试在wasm中塞图片

在wasm里塞图片的原理是,wasm程序吐出图片文件数据的TypedArray,再如上方法构造出blob最后生成图片就可以了
首先来一段简单的c程序将数据段存储起来:

點擊在新視窗中瀏覽此圖片

定义了一个这样的玩意儿,然后编译为wasm:

1. 通过emscripten从c编译出来的wasm也太大了。一个头文件不引入,纯程序也有9kb,加上这张图片5kb,感觉得不偿失。
2. c源码中也没法直接拷贝数据段字符串,得如上转换成数据的形式,不过作为编译语言对编译结果没有印象而已,但是图片开发处理过程就麻烦了。
3. 经过对编译结果wasm的检查,和预想的一样,数据段是密布的排列在一起的,能完全实现压缩效果。效果如图(大小端倒过来了):

點擊在新視窗中瀏覽此圖片

既然符合预期的话,可以预想:

1. 如果未来WebAssembly进行js开发成为主流,在WebAssembly程序中像c语言一样将图片塞进程序数据段里就可以了。图片在编译后最终的大小就是实际大小了。
2. 如果未来还是以js为主流,多个小图片可以以wasm作为资源包的形式提供。当然这样的话wasm用啥编译只要能塞数据就像了。这种情况下建议直接编写S-表达式,用形如:
(data $d0 (i32.const 1024) "GNP\89\0a\1a\0a\0d\0d\00\00\00RDHIZ\04\00\00\ef\02\00\00\00\00\03\08Q7b\00\02")

的形式将大量数据密布储存在数据段中,并且再编写TypedArray数据的导出方法即可。

其他

因此,结论上,在目前主流的技术栈上,base64来硬编码图片还是是唯一的办法。但是等WebAssembly普及之后,理论上可以在那相关程序中储存原始图片文件数据了。

另外,目前真的要做加载大量资源“尽量减少请求量”并且“追求极小流量”的话,目前页游主流的“资源包”+分段并行下载,是合理的办法。开资源包就和普通的开文件方法一致了,可以参考过去的博客内容。
关键词:base64
logo