You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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} />
    </>
)      

问题根源

  1. 渲染时机不匹配:useEffect中创建Fabric实例后立即调用printCanvas,此时Fabric尚未完成画布内容的渲染,直接调用原生Canvas的toDataURL会获取空白图像。
  2. 冗余且不可靠的元素获取:已经通过useRef拿到Canvas元素,却又用document.getElementById重复获取,组件重渲染时可能出现引用不一致的问题。
  3. 打印窗口事件绑定错误: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;

关键修复点

  • 新增fabricCanvas ref保存Fabric实例,直接调用其toDataURL方法,比原生Canvas更能保证获取到渲染完成的图像。
  • 在useEffect中调用canvas.renderAll()强制完成渲染,再通过requestAnimationFrame延迟执行打印,确保画布内容已绘制到Canvas上。
  • 重构打印窗口的HTML内容,使用模板字符串简化拼接,添加基础样式优化打印预览效果。
  • 将onload事件改为标准的事件监听,确保图像完全加载后再执行打印,添加setTimeout避免打印对话框被窗口关闭操作打断。
  • 调用printWin.document.close(),确保文档写入完成并触发load事件。

内容的提问来源于stack exchange,提问作者Reza

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 01:04:59