假设一个应用场景:由于某些特殊原因从服务端请求到图片路径(图片被存储在服务器上),要求通过该路径获取对应图片的 base64 dataURL。在这个场景中,我们首先推断该图片路径是可访问的,同时还需要一种将图片转换到 dataURL 的方法。我们如何实现它呢?
dataURL
先大致回顾下正统的 dataURL 的语法,这有助于我们检验转换后的内容是否正确。一个完整的 dataURI 应该是这样的:
data:[<mediatype>][;base64],<data>
其中mediatype
声明了文件类型,遵循MIME规则,如“image/png
”、“text/plain
”;之后是编码类型,这里我们只涉及 base64;紧接着就是文件编码后的内容了。我们常常在 HTML 里看到img
标签的src
会这样写:
src="data:image/gif;base64,R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapyuvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97Vriy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7"
这个img
引用的就是以 base64 编码的 dataURL 了,只要浏览器支持,就可以被解码成声明格式的图片并渲染出来。
.toDataURL()
这是一个功能函数,FileReader
对象也有类似的方法,比如.readAsDataURL()
,然而它只接受file
或blob
类型,而这两种类型一般只能通过<input[type=file]>
元素的files
属性获取,或者用Blob()
构造函数手工创建一个新的对象。尴尬的是我们当前只有图片路径,受制于浏览器的安全策略,<input[type=file]>
的files
属性是只读的,而Blob()
构造函数只接受文件内容,两种方式都无法通过图片路径直接获取。上文中假设的应用场景迫使我们必先考虑如何通过路径获取到图片内容。<img>
是可以的,并且可以被绘制到<canvas>
中,而<canvas>
正巧拥有.toDataURL()
方法。
万事具备,我们只需要把<img>
获取到的图片放到<canvas>
里再通过.toDataURL()
方法转化下,就可以得到以 base64 编码的 dataURL。来看这个方法的语法:
canvas.toDataURL([type, encoderOptions]);
canvas 是 DOM 元素<canvas>
对象;参数type
指定图片类型,如果指定的类型不被支持则以默认值image/png
替代;encoderOptions
可以为image/jpeg
或image/webp
类型的图片设置图片质量,取值0-1
,超出则以默认值0.92
替代。
需要注意的是,图片加载是异步的,在转换成 dataURL 前必须先确保图片成功加载到,否则让 canvas 即刻执行绘制可能失败,从而导致转换 dataURL 失败。于是.toDataURL()
方法应该写在<img>
的onload
事件中,以确保 canvas 的绘制工作在图片下载完成后开始。另一个问题是<img>
图片渲染到<canvas>
上也需要一个过程,好在.drawImage()
方法是同步的,只有在 canvas 绘制完成后才会执行后续如.toDataURL()
的操作。现在就来实现一个功能函数:
function getBase64(url){
// 通过构造函数来创建的 img 实例
// 在赋予 src 值后就会立刻下载图片
// 相比 createElement() 创建 <img> 省去了 append(),也就避免了文档冗余和污染
let dataURL = ''
let img = new Image();
img.src = url;
img.onload = () => { // 要先确保图片完整获取到,这是个异步处理
let canvas = document.createElement('canvas'); // 创建canvas元素
let [width,height] = [img.width,img.height]; // 确保canvas的尺寸和图片一样
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(img, 0, 0, width, height); // 将图片绘制到canvas中
dataURL = canvas.toDataURL('image/jpeg'); // 转换图片为dataURL
}
}
一个可供随时调用的转换函数完成了,它会在图片被加载后返回一整个 dataURL 字符串。
完善
onload
事件确保了转换任务在图片加载后执行,却又带来了新问题——dataURL 只有在图片加载完成后才会返回,我们是无法精准确定图片完成加载的时间的。如果后续要对 dataURL 做相关处理(比如传递到其他服务器)的话,添加一个回调是必要的,这能确保后续处理任务在成功得到 dataURL 之后执行,我们修改一下getBase64()
:
function getBase64(url, callback){ //添加一个回调参数
...
img.onload = () => {
...
canvas.getContext('2d').drawImage(img, 0, 0, width, height);
dataURL=canvas.toDataURL('image/jpeg');
callback&&callback(dataURL); //调用回调函数
}
}
在执行时添加回调:
let imgURL = '//upload.jianshu.io/users/upload_avatars/555630/fdd1b798e6b0.jpg';
getBase64(imgURL, dataURL => {
console.log(dataURL)
});
就是这样。如果不考虑兼容性的话,或许我们可以用 promise 和 generator 来实现,再添加一些错误处理就更完美了。