/** * 图片纵向合成 —— 把多张图表 PNG 顺序拼接为一张竖版长图。 * 公众号文章在手机上以竖版窗口预览,横版单图显示过小, * 两张上下拼接后(2400×~2800)接近满屏,阅读体验明显更好。 * 纯浏览器 Canvas API,无依赖。 */ const DEFAULTS = { gap: 96, background: '#fff' }; // gap 为设备像素:@2x 导出下 96 = 视觉 48px function loadImage(src) { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve(img); img.onerror = () => reject(new Error('图片解码失败')); img.src = src; }); } /** * 按顺序纵向拼接一组图片 dataURL,返回合成后的 canvas。 * 宽度取各图最大值;宽度不一致的图等比缩放对齐(正常流程导出的图宽度一致,不会触发缩放)。 */ export async function mergeImages(sources, options = {}) { const { gap, background } = { ...DEFAULTS, ...options }; if (!sources.length) throw new Error('没有可合并的图片'); const images = await Promise.all(sources.map(loadImage)); const width = Math.max(...images.map((img) => img.width)); const heights = images.map((img) => Math.round(img.height * width / img.width)); const height = heights.reduce((sum, h) => sum + h, 0) + gap * (images.length - 1); const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.fillStyle = background; ctx.fillRect(0, 0, width, height); let y = 0; images.forEach((img, index) => { ctx.drawImage(img, 0, y, width, heights[index]); y += heights[index] + gap; }); return canvas; } /** canvas → PNG Blob 触发下载(Blob URL 比超长 dataURL 下载更稳) */ export function downloadCanvas(canvas, filename) { canvas.toBlob((blob) => { const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = filename.endsWith('.png') ? filename : `${filename}.png`; anchor.click(); setTimeout(() => URL.revokeObjectURL(url), 10_000); }, 'image/png'); }