web_ppt / frontend /src /utils /canvasRenderer.ts
CatPtain's picture
Upload canvasRenderer.ts
ffe4e3d verified
raw
history blame
6.88 kB
/**
* Canvas渲染工具
* 用于将DOM元素渲染为Canvas并转换为Base64图像
*/
// Dynamic import for html2canvas to resolve bundling issues
const getHtml2Canvas = async () => {
const { default: html2canvas } = await import('html2canvas');
return 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<HTMLCanvasElement>
*/
export async function renderElementToCanvas(
element: HTMLElement,
options: RenderOptions = {}
): Promise<HTMLCanvasElement> {
const {
scale = 1,
backgroundColor = null,
useCORS = true,
timeout = 5000
} = options;
return new Promise(async (resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error('Canvas rendering timeout'));
}, timeout);
try {
// 动态导入html2canvas
const html2canvas = await getHtml2Canvas();
// 使用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<HTMLCanvasElement>
*/
export async function renderSVGToCanvas(
svgElement: SVGElement,
options: RenderOptions = {}
): Promise<HTMLCanvasElement> {
// 对于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<string> Base64数据URL
*/
export async function renderElementToBase64(
element: HTMLElement,
options: RenderOptions = {}
): Promise<string> {
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
}
};
}