前端要把一张 <img> 转成 Base64——通常是为了上传、缓存、内联到别的地方。最直接的方式是 canvas。
基础版:canvas.toDataURL
function imgToBase64(img, type = "image/png") {
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0);
return canvas.toDataURL(type);
}
const img = document.querySelector("img");
img.onload = () => console.log(imgToBase64(img));
关键点:
- 用
naturalWidth/naturalHeight,不是width/height(后者是显示尺寸,会被 CSS 缩放) - 图片必须加载完成才能 draw,判
img.complete或者监听onload type可选image/png、image/jpeg、image/webp- JPEG 可以传第二个参数控制画质:
canvas.toDataURL("image/jpeg", 0.8)
只有 URL 没有 img 元素
async function urlToBase64(url) {
const img = new Image();
img.crossOrigin = "anonymous"; // 关键,让 canvas 不 tainted
img.src = url;
await new Promise((res, rej) => {
img.onload = res;
img.onerror = rej;
});
const canvas = document.createElement("canvas");
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
canvas.getContext("2d").drawImage(img, 0, 0);
return canvas.toDataURL("image/png");
}
跨域的 tainted 坑
跨域图片经 canvas 一处理,toDataURL 直接抛:
SecurityError: Tainted canvases may not be exported
原因:canvas 一旦画了跨域资源就”被污染”,浏览器不让你导出数据(防止读取用户其它站的私有图片)。
要出数据,需要满足:
- 图片服务器返回
Access-Control-Allow-Origin: *(或匹配你的域) <img>元素设置crossOrigin="anonymous"
如果对方服务器没配 CORS,只能:
- 走自己的 Nginx 反代加 CORS 头
fetch+blob+FileReader绕开 canvas
更稳的方案:fetch + blob + FileReader
async function urlToBase64(url) {
const res = await fetch(url);
const blob = await res.blob();
return await new Promise(resolve => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
}
返回的就是:
data:image/png;base64,iVBORw0KGgoAAAANS...
优势:
- 不经过 canvas,没有 tainted 问题
- 支持任何图片格式,包括 GIF 动图(canvas 会把动图变成第一帧)
- 保持原始质量,不会重新压缩
但 fetch 依然受 CORS 约束——服务器不配 CORS 时仍然会失败,只是不再看到”tainted”的错误,而是看到网络请求被拦。
想跳过 CORS 的最后一招
在自己的服务端加个代理:
浏览器 → your-server/proxy?url=xxx → 目标图片
服务端拿到图片,加上 CORS 头再回给浏览器。前端就走同源了。
Base64 有多大
Base64 编码后体积变大约 33%。10 KB 的图变 13.3 KB 左右。做本地缓存 / 内联小图(favicon、图标)合适;大图别用 Base64,走 URL 更省。
一句话总结
canvas.toDataURL 一行搞定,跨域首选 fetch → blob → FileReader。用 canvas 就得先解决 CORS,别硬撞 tainted 报错。
