假设一个应用场景:由于某些特殊原因从服务端请求到图片路径(图片被存储在服务器上),要求通过该路径获取对应图片的 base64 dataURL。在这个场景中,我们首先推断该图片路径是可访问的,同时还需要一种将图片转换到 dataURL 的方法。我们如何实现它呢?

dataURL

先大致回顾下正统的 dataURL 的语法,这有助于我们检验转换后的内容是否正确。一个完整的 dataURI 应该是这样的:

data:[][;base64],

其中mediatype声明了文件类型,遵循MIME规则,如“image/png”、“text/plain”;之后是编码类型,这里我们只涉及 base64;紧接着就是文件编码后的内容了。我们常常在 HTML 里看到img标签的src会这样写:

src=""

这个img引用的就是以 base64 编码的 dataURL 了,只要浏览器支持,就可以被解码成声明格式的图片并渲染出来。

.toDataURL()

这是一个功能函数,FileReader对象也有类似的方法,比如.readAsDataURL(),然而它只接受file或blob类型,而这两种类型一般只能通过元素的files属性获取,或者用Blob()构造函数手工创建一个新的对象。尴尬的是我们当前只有图片路径,受制于浏览器的安全策略,的files属性是只读的,而Blob()构造函数只接受文件内容,两种方式都无法通过图片路径直接获取。上文中假设的应用场景迫使我们必先考虑如何通过路径获取到图片内容。是可以的,并且可以被绘制到中,而正巧拥有.toDataURL()方法。

万事具备,我们只需要把获取到的图片放到里再通过.toDataURL()方法转化下,就可以得到以 base64 编码的 dataURL。来看这个方法的语法:

canvas.toDataURL([type, encoderOptions]);

canvas 是 DOM 元素对象;参数type指定图片类型,如果指定的类型不被支持则以默认值image/png替代;encoderOptions可以为image/jpeg或image/webp类型的图片设置图片质量,取值0-1,超出则以默认值0.92替代。

需要注意的是,图片加载是异步的,在转换成 dataURL 前必须先确保图片成功加载到,否则让 canvas 即刻执行绘制可能失败,从而导致转换 dataURL 失败。于是.toDataURL()方法应该写在的onload事件中,以确保 canvas 的绘制工作在图片下载完成后开始。好在.drawImage()方法是同步的,只有在 canvas 绘制完成后才会执行后续操作,比如.toDataURL()。现在就来实现一个功能函数:

function getBase64(url){
 //通过构造函数来创建的 img 实例,在赋予 src 值后就会立刻下载图片,相比 createElement() 创建 省去了 append(),也就避免了文档冗余和污染
var Img = new Image(),
dataURL='';
Img.src=url;
Img.οnlοad=function(){ //要先确保图片完整获取到,这是个异步事件
var canvas = document.createElement('canvas'), //创建canvas元素
width=Img.width, //确保canvas的尺寸和图片一样
height=Img.height;
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.οnlοad=function(){
...
canvas.getContext('2d').drawImage(Img,0,0,width,height);
dataURL=canvas.toDataURL('image/jpeg');
callback&&callback(dataURL); //调用回调函数
};
}

在执行时添加回调:

getBase64('//upload.jianshu.io/users/upload_avatars/555630/fdd1b798e6b0.jpg',(dataURL)=>{
console.log(dataURL);
});

就是这样。如果不考虑兼容性的话,或许我们可以用 promise 和 generator 来实现,再添加一些错误处理就更完美了。