export default class Haibao { constructor(width, height, color = '#fff') { this.canvas = document.createElement('canvas'); this.ctx = this.canvas.getContext('2d', { alpha: true }); this.canvas.width = width; this.canvas.height = height; // this.ctx.fillStyle = color; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height) this.zIndex = 0 this.imagePromises = [] this.images = []; // 存储待合成的图片信息 } /** * 添加图片到合成队列(现在返回Promise) * @param {string|HTMLImageElement} image 图片URL或Image对象 * @param {number} x 绘制X坐标 * @param {number} y 绘制Y坐标 * @returns {Promise} 图片加载完成的Promise */ add (image, x, y, width = 'auto', height = 'auto', index) { this.zIndex++ const zIndex = index ? index : this.zIndex const loadPromise = this._createLoadPromise(image).then(img => { this.images.push({ img, x, y, width, height, zIndex }); }); this.imagePromises.push(loadPromise); return loadPromise; } text (text, x, y, opts) { const { font = '16px sans-serif', color = '#000', align = 'center' } = opts || {}; this.ctx.font = font; this.ctx.fillStyle = color; const drawText = (text, x, y) => { this.ctx.fillText(text, x, y); }; const totalWidth = this.ctx.measureText(text).width; if (align === 'center') { x -= totalWidth / 2; } drawText(text, x, y); return this } async draw (mask = 'source-over') { await Promise.all(this.imagePromises); // 绘制所有图片 this.images.sort((a, b) => a.zIndex - b.zIndex) this.images.forEach(({ img, x, y, width, height, zIndex }, idx) => { const w = width === 'auto' ? img.width : width const h = height === 'auto' ? img.height : height this.ctx.drawImage(img, x, y, w, h); if (idx === 0) { this.ctx.globalCompositeOperation = mask } else { this.ctx.globalCompositeOperation = 'source-over' } }); return this; } /** * 生成合成后的图片(返回Promise) * @returns {Promise} 合成后的Base64图片数据 */ async generate ({ mimeType = 'image/jpeg', quality = .8 }) { const validTypes = ['image/png', 'image/jpeg', 'image/webp']; mimeType = validTypes.includes(mimeType) ? mimeType : 'image/png'; quality = Math.min(1, Math.max(0, Number(quality))) || .8; const b64 = this.canvas.toDataURL(mimeType, quality) this.images.length = 0; this.imagePromises.length = 0; return b64 } // 创建加载Promise(私有方法) _createLoadPromise (image) { if (typeof image === 'string') { return this._loadImageFromUrl(image); } else if (image instanceof HTMLImageElement) { return this._handleExistingImage(image); } return Promise.reject(new Error('Invalid image type')); } // 从URL加载图片 _loadImageFromUrl (url) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'anonymous'; img.src = url; img.onload = () => resolve(img); img.onerror = reject; }); } // 处理已存在的Image对象 _handleExistingImage (img) { return new Promise((resolve, reject) => { if (img.complete && img.naturalHeight !== 0) { resolve(img); } else { img.onload = () => resolve(img); img.onerror = reject; } }); } }