Canvas的drawImage是唯一能真正合成单张图的方案,因CSS布局仅视觉并排、无法导出文件或像素级控制;必须等所有图片加载完成,用Promise.all统一处理onload与onerror,并按naturalWidth/Height计算画布尺寸和坐标。

Canvas 的 drawImage 是唯一能真正合成一张图的方案,CSS Grid 或 background-image 只是视觉并排,导出不了单张文件,也做不了像素级控制。
为什么不能直接用 img + CSS 排列?
CSS 布局(比如 Grid、Flex 或 float)只是把多张 img 标签在页面上“摆整齐”,DOM 结构仍是分离的。你无法:
– 调用 canvas.toDataURL() 导出为一张 PNG/JPEG
– 统一缩放/裁剪每张图到指定像素尺寸
– 设置透明背景或自定义画布色
– 处理跨域图片(img.crossOrigin 对纯 DOM 无效)
– 在拼接时插入间距、边框、文字等合成元素
drawImage 拼接必须等所有图片加载完成
常见错误:循环创建 Image 后立刻 drawImage,结果部分区域空白——因为 img.onload 是异步的,img.width/img.height 在未加载完时为 0。
- 必须用
Promise.all包裹每张图的加载逻辑,失败时reject明确报错位置 - 别用
img.complete+setTimeout轮询,不可靠且易漏掉 error 状态 - 跨域图片(如 CDN、图床)务必加
img.crossOrigin = 'anonymous',否则toDataURL()抛SecurityError - 取真实尺寸用
img.naturalWidth/img.naturalHeight,不是img.width(那是 CSS 渲染宽)
横向/纵向拼接的坐标和画布尺寸怎么算?
没有自动布局,全靠手动累加。关键不是“怎么画”,而是“画布多大”和“下一张从哪开始”。
立即学习“前端免费学习笔记(深入)”;
- 横向拼接:画布宽度 = 所有
img.naturalWidth之和 +gap * (n - 1);高度取所有图中最大naturalHeight - 纵向拼接:画布高度 = 所有
img.naturalHeight之和 +gap * (n - 1);宽度取所有图中最大naturalWidth - 每张图的
dx/dy要递推计算,例如横向:第一张dx = 0,第二张dx = img1.naturalWidth + gap,第三张dx = img1.naturalWidth + img2.naturalWidth + gap * 2 - 如果要等比缩放某张图填满固定区域,用四参数版
drawImage(img, dx, dy, dWidth, dHeight),但注意缩放后可能模糊,优先保持原始像素绘制
导出前最容易忽略的三件事
拼完调 toDataURL() 却得到黑图、白图或 SecurityError,大概率栽在这三点:
- 画布
width/height属性没设——只改 CSS 尺寸无效,必须赋值给canvas.width和canvas.height - 背景没清空——多次拼接复用同一 canvas 时,旧内容残留,需在绘制前执行
ctx.clearRect(0, 0, canvas.width, canvas.height)或用fillRect填充背景色 - 跨域图没处理或服务端没配 CORS header——即使加了
crossOrigin = 'anonymous',若资源服务器返回的响应头不含Access-Control-Allow-Origin,仍会静默失败
真正难的不是写几行 drawImage,而是把加载、尺寸计算、坐标偏移、错误兜底、跨域适配这整条链路串稳。少一个环节,工具就卡在用户上传完图片却点不动“生成”按钮。



















