svg 图片的上传下载
背景
最近有个需求:
- 根据传入的 code 生成条形码 svg 图片
- 将 svg 图片上传至 cdn
- 展示图片时需要点击按钮后保存至本地
生成条形码
这里借助一个开源的仓库
import JsBarcode from 'jsbarcode';
const generateBarcode = async (barcode, options = {}) => {
// 创建一个 svg 元素
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
// 甚至宽高
svg.setAttribute('width', `${width}px`);
svg.setAttribute('height', `${height}px`);
// 生成条形码 svg 的图片
JsBarcode(svg, barcode, options);
return svg
}
图片上传
svg 转 canvas
由于服务端同学提供的接口只支持 canvas,因此首先要将 svg 转成 canvas:
const svgToCanvas = (svg) => {
// svg.outerHTML 获取 svg 元素的 html 字符串
// btoa 将 svg 转成 base64
const href = `data:image/svg+xml;base64,${window.btoa(
unescape(encodeURIComponent(svg.outerHTML)),
)}`;
// 构建一个 Image
const img = new Image();
img.width = width;
img.height = height;
img.onload = () => {
// 构建一个 canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillRect(0, 0, width, height);
// 将 图片画到 canvas 中
ctx.drawImage(img, 0, 0, width, height);
// 转成 jpeg 的 base64
const dataURI = canvas.toDataURL('image/jpeg');
// 上传
upload(dataURI);
}
img.src = href;
}
Data URL
通过 `canvas.toDataURL 将 canvas 转成 Data URL,一段 Data URL 由 4 个部分组成:
data:[<mediatype>][;base64],<data>
// 例如: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC"
Base64的解码与编码
atob() 函数能够解码通过base-64编码的字符串数据。
btoa() 函数能够从二进制数据“字符串”创建一个base-64编码的ASCII字符串。
Base64 转 Blob
上传图片文件需要将 base64 转成 Blob 对象,有两种方法:
######Base64 转 Blob 第一种方法:
function dataURItoBlob(dataURI: string) {
// 通过 atob 转回 string
const byteString = atob(dataURI.split(',')[1]);
// 获得文件类型
const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// 转成
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
// 转成 blob
const bb = new Blob([ab], { type: mimeString });
return bb;
}
思路:
- 拆解 data uri,通过 atob 将 base64 解码成字符串
- 拆解 data uri,获得图片的格式
- 创建一个 arraybuffer
- 通过TypedArray(这里使用 Uint8Array)将字符串的内容写入 arraybuffer
- 将 array buffer 转成 blob
Base64 转 Blob 第二种方法:
通过 fetch 将 dataURI 转成 blob:
const bb = await fetch(dataURI).then((res) => res.blob())
上传图片
构造 FormData 上传图片文件:
const upload = (dataURI: string) => {
// 创建form对象
const formData = new FormData();
// 通过 append 向 form 对象添加数据
formData.append('file', dataURItoBlob(dataURI));
const { data } = await axios.post('/api/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data',
'X-Requested-With': 'XMLHttpRequest',
},
});
}
下载图片
保存图片到本地:
const download = (fileUrl, name) => {
const x = new XMLHttpRequest();
x.open('GET', fileUrl, true);
x.responseType = 'blob';
x.onload = () => {
// 转为 objectUrl
const url = window.URL.createObjectURL(x.response);
// 构建一个 a 标签
const a = document.createElement('a');
a.href = url;
a.download = name;
a.click();
};
x.send();
};
利用 a 标签的 download 属性可以实现图片的下载,但是有个限制是 href 必须是同源的地址,否则无法下载。
因此这里通过 get 请求获得图片的 Blob 格式,并用 window.URL.createObjectURL 将 Blob 转成 objectUrl 解决了跨域问题。之后就可以用 a 标签的 download 属性下载文件。
补充几个概念
blob
上传图片时,我们实现了一个函数 dataURItoBlob, 通过这个函数来将 base64 转为 Blob,最终上传给接口的 formData 中的 file 也是一个 Blob 对象。
什么是 Blob 对象?
一个 Blob对象表示一个不可变的, 原始数据的类似文件对象。Blob 表示的不一定是JavaScript原生格式的数据。
File接口基于Blob,继承了 blob 的功能并将其扩展使其支持用户系统上的文件。
Blob 属于 web 提供的 api,因此 mdn 上说的 Blob 表示的不一定是JavaScript原生格式的数据。
File
File 继承自 Blob,可以看作是特殊的 Blob。常见的获得 File 对象的方法有 input 标签上选择文件后返回的 FileList 对象。
Blob 转 URL
在下载时,我们通过 get 请求获得了图片的 blob 格式,之后通过 URL.createObjectURL 转成 objectUrl,这样就可以像普通的 url,赋值给 img 的 src 属性或者 a 标签的 href 属性。