/** * Canvas渲染工具 * 用于将DOM元素渲染为Canvas并转换为Base64图像 */ import html2canvas from 'html2canvas'; // 渲染选项接口 interface RenderOptions { scale?: number; backgroundColor?: string | null; useCORS?: boolean; timeout?: number; format?: 'png' | 'jpeg' | 'webp'; quality?: number; } /** * 将DOM元素渲染到Canvas * @param element 要渲染的DOM元素 * @param options 渲染选项 * @returns Promise */ export async function renderElementToCanvas( element: HTMLElement, options: RenderOptions = {} ): Promise { const { scale = 1, backgroundColor = null, useCORS = true, timeout = 5000 } = options; return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error('Canvas rendering timeout')); }, timeout); try { // 使用html2canvas进行渲染 html2canvas(element, { scale, backgroundColor, useCORS, allowTaint: !useCORS, foreignObjectRendering: true, logging: false, width: element.offsetWidth, height: element.offsetHeight, windowWidth: element.offsetWidth, windowHeight: element.offsetHeight }).then((canvas) => { clearTimeout(timeoutId); resolve(canvas); }).catch((error) => { clearTimeout(timeoutId); reject(new Error(`html2canvas rendering failed: ${error.message}`)); }); } catch (error) { clearTimeout(timeoutId); reject(error); } }); } /** * 将SVG元素渲染到Canvas * @param svgElement SVG元素 * @param options 渲染选项 * @returns Promise */ export async function renderSVGToCanvas( svgElement: SVGElement, options: RenderOptions = {} ): Promise { // 对于SVG元素,我们可以直接使用html2canvas // 或者使用传统的SVG序列化方法 const { scale = 1, backgroundColor = null, timeout = 5000 } = options; return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error('SVG Canvas rendering timeout')); }, timeout); try { // 获取SVG尺寸 const rect = svgElement.getBoundingClientRect(); const width = rect.width || svgElement.clientWidth || 300; const height = rect.height || svgElement.clientHeight || 200; // 创建Canvas const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (!ctx) { clearTimeout(timeoutId); reject(new Error('Failed to get 2D context')); return; } canvas.width = width * scale; canvas.height = height * scale; ctx.scale(scale, scale); // 设置背景色 if (backgroundColor) { ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, width, height); } // 获取SVG的XML字符串 const svgData = new XMLSerializer().serializeToString(svgElement); const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' }); const url = URL.createObjectURL(svgBlob); const img = new Image(); img.onload = () => { try { ctx.drawImage(img, 0, 0, width, height); URL.revokeObjectURL(url); clearTimeout(timeoutId); resolve(canvas); } catch (error) { URL.revokeObjectURL(url); clearTimeout(timeoutId); reject(new Error(`Failed to draw SVG to Canvas: ${error}`)); } }; img.onerror = () => { URL.revokeObjectURL(url); clearTimeout(timeoutId); reject(new Error('SVG image loading failed')); }; img.src = url; } catch (error) { clearTimeout(timeoutId); reject(new Error(`SVG serialization failed: ${error}`)); } }); } /** * 将Canvas转换为Base64数据URL * @param canvas Canvas元素 * @param options 转换选项 * @returns Base64数据URL字符串 */ export function canvasToBase64( canvas: HTMLCanvasElement, options: RenderOptions = {} ): string { const { format = 'png', quality = 0.95 } = options; try { if (format === 'jpeg') { return canvas.toDataURL('image/jpeg', quality); } else if (format === 'webp') { return canvas.toDataURL('image/webp', quality); } else { return canvas.toDataURL('image/png'); } } catch (error) { throw new Error(`Canvas to Base64 conversion failed: ${error}`); } } /** * 直接将DOM元素渲染为Base64图像 * @param element 要渲染的DOM元素 * @param options 渲染和转换选项 * @returns Promise Base64数据URL */ export async function renderElementToBase64( element: HTMLElement, options: RenderOptions = {} ): Promise { try { console.log('Starting element to Base64 conversion:', { element: element.tagName, className: element.className, options }); const canvas = await renderElementToCanvas(element, options); const base64 = canvasToBase64(canvas, options); console.log('Element to Base64 conversion completed:', { canvasSize: `${canvas.width}x${canvas.height}`, base64Length: base64.length, preview: base64.substring(0, 100) + '...' }); return base64; } catch (error) { console.error('Element to Base64 conversion failed:', error); throw new Error(`Element rendering failed: ${error}`); } } /** * 检查浏览器是否支持Canvas渲染 * @returns boolean */ export function isCanvasRenderSupported(): boolean { try { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); return !!(ctx && typeof ctx.drawImage === 'function' && typeof window !== 'undefined'); } catch { return false; } } /** * 获取元素的有效尺寸 * @param element DOM元素 * @returns 尺寸对象 */ export function getElementDimensions(element: HTMLElement) { const rect = element.getBoundingClientRect(); const computedStyle = window.getComputedStyle(element); return { width: rect.width || element.offsetWidth || parseFloat(computedStyle.width) || 0, height: rect.height || element.offsetHeight || parseFloat(computedStyle.height) || 0, clientWidth: element.clientWidth, clientHeight: element.clientHeight, offsetWidth: element.offsetWidth, offsetHeight: element.offsetHeight, boundingRect: { width: rect.width, height: rect.height, top: rect.top, left: rect.left } }; }