React项目中如何用Fabric.js打印Canvas?求助排查canvasEl问题
React + Fabric.js Canvas打印不显示图像问题修复
问题描述
在React项目中声明了id为mycanvas的Canvas,使用Fabric.js初始化并添加文本内容后,调用打印功能时无法显示Canvas图像,怀疑canvasEl变量的使用存在问题,相关代码如下:
const canvasEl = useRef<HTMLCanvasElement>(null); useEffect(() => { const options = { width: canvasWidth, height: canvasHeight, backgroundColor: 'white' } const canvas = new fabric.Canvas(canvasEl.current, options) const tInput = new fabric.Text('Hello', { left: 100, top: 200, fill: 'Black', fontSize: 60 }); canvas.add(tInput) printCanvas() return () => { canvas.dispose();} }, [props.selectedMode, props.shouldRemoveSelectedObject]) const printCanvas = () => { const canvasElement = document.getElementById('mycanvas') as HTMLCanvasElement var dataUrl = canvasElement.toDataURL() let windowContent = '<!DOCTYPE html>' windowContent += '<html>' windowContent += '<head><title>Print canvas</title></head>' windowContent += '<body>' windowContent += '<h1>This header is visible in preview</h1>' windowContent += '<img src="' + dataUrl + '" onload=window.print();window.close();>' windowContent += '</body>' windowContent += '</html>' const printWin: any = window.open('', '', 'width=1000,height=1000'); printWin.document.open(); printWin.document.write(windowContent); } return ( <> <canvas id="mycanvas" width="300" height="300" ref={canvasEl} /> </> )
问题根源
- 渲染时机不匹配:
useEffect中创建Fabric实例后立即调用printCanvas,此时Fabric尚未完成画布内容的渲染,直接调用原生Canvas的toDataURL会获取空白图像。 - 冗余且不可靠的元素获取:已经通过
useRef拿到Canvas元素,却又用document.getElementById重复获取,组件重渲染时可能出现引用不一致的问题。 - 打印窗口事件绑定错误:
img的onload属性写法存在语法风险,且未确保图像完全加载就执行打印+关闭窗口,可能导致图像未渲染完成。
修复后的代码
import React, { useRef, useEffect } from 'react'; import { fabric } from 'fabric'; const CanvasPrintComponent = (props: any) => { const canvasEl = useRef<HTMLCanvasElement>(null); const fabricCanvas = useRef<fabric.Canvas | null>(null); const canvasWidth = 300; const canvasHeight = 300; useEffect(() => { if (!canvasEl.current) return; const options = { width: canvasWidth, height: canvasHeight, backgroundColor: 'white' }; const canvas = new fabric.Canvas(canvasEl.current, options); fabricCanvas.current = canvas; const tInput = new fabric.Text('Hello', { left: 100, top: 200, fill: 'Black', fontSize: 60 }); canvas.add(tInput); // 确保Fabric完成渲染后再触发打印 canvas.renderAll(); requestAnimationFrame(() => { printCanvas(); }); return () => { canvas.dispose(); fabricCanvas.current = null; }; }, [props.selectedMode, props.shouldRemoveSelectedObject]); const printCanvas = () => { if (!fabricCanvas.current) return; // 直接使用Fabric实例的toDataURL,确保获取渲染完成的图像 const dataUrl = fabricCanvas.current.toDataURL({ format: 'png', quality: 1 }); // 用模板字符串简化HTML拼接,添加基础样式优化打印体验 const windowContent = ` <!DOCTYPE html> <html> <head> <title>Print canvas</title> <style> body { margin: 2rem; padding: 0; } img { max-width: 100%; height: auto; } </style> </head> <body> <h1>This header is visible in preview</h1> <img src="${dataUrl}" /> <script> // 确保图像完全加载后再执行打印 window.addEventListener('load', () => { window.print(); // 延迟关闭窗口,避免打印对话框被强制关闭 setTimeout(() => window.close(), 100); }); </script> </body> </html> `; const printWin = window.open('', '', 'width=1000,height=1000') as Window; printWin.document.open(); printWin.document.write(windowContent); printWin.document.close(); // 必须调用close触发load事件 }; return ( <> <canvas id="mycanvas" width={canvasWidth} height={canvasHeight} ref={canvasEl} /> </> ); }; export default CanvasPrintComponent;
关键修复点
- 新增
fabricCanvasref保存Fabric实例,直接调用其toDataURL方法,比原生Canvas更能保证获取到渲染完成的图像。 - 在
useEffect中调用canvas.renderAll()强制完成渲染,再通过requestAnimationFrame延迟执行打印,确保画布内容已绘制到Canvas上。 - 重构打印窗口的HTML内容,使用模板字符串简化拼接,添加基础样式优化打印预览效果。
- 将
onload事件改为标准的事件监听,确保图像完全加载后再执行打印,添加setTimeout避免打印对话框被窗口关闭操作打断。 - 调用
printWin.document.close(),确保文档写入完成并触发load事件。
内容的提问来源于stack exchange,提问作者Reza
相关产品推荐
相关产品推荐

