Puppeteer生成PDF时Chart.js canvas转图片失效显示异常如何解决
问题背景
我有一个使用Puppeteer生成PDF格式大型报告的JS项目,除canvas相关内容外其余功能运行均正常。
React中Chart.js的实现逻辑
useEffect((): void => { if (refChart && refChart.current) { const newChartInstance = new Chart(refChart.current, chartConfig); newChartInstance.options.animation = { onComplete: (): void => { if (refImage && refImage.current) { refImage.current.src = newChartInstance.toBase64Image(); } }, }; } }, [refChart]); return ( <> <canvas ref={refChart} style={{ display: (printing) ? 'none' : 'block' }} width="100%" /> <img alt="printing chart" ref={refImage} className={classes.chartImage} style={{ display: (printing) ? 'block' : 'none', }} /> </> );
异常表现
- 直接访问页面、手动按CTRL+P打印时,图表会正常转为图片显示,效果符合预期:

- 即便强制将printing参数设为true,Puppeteer导出的PDF中仍出现canvas显示破损、尺寸错误的问题,未正常替换为生成的图片:

解决方案
问题根源
该问题本质是Puppeteer默认的PDF生成时机早于两个关键节点:1. Chart.js动画执行完毕、Base64图片生成并挂载到img节点 2. 生成的Base64图片资源加载完成。同时无头浏览器环境下canvas的渲染尺寸和普通浏览器存在差异,仅靠JS控制display属性容易出现样式逃逸。
具体修复步骤
1. 调整Chart.js配置,优先保障导出逻辑执行
关闭动画避免无头环境下动画回调不触发,同时主动触发Base64生成逻辑,无需等待动画回调:
useEffect((): void => { if (refChart && refChart.current) { const newChartInstance = new Chart(refChart.current, { ...chartConfig, options: { ...chartConfig.options, // 直接关闭动画,避免无头环境回调异常 animation: false, } }); // 主动生成Base64赋值给图片,无需等待回调 if (refImage && refImage.current) { refImage.current.src = newChartInstance.toBase64Image(); } } }, [refChart, printing]);
2. 补充样式兜底,彻底隐藏打印环境下的canvas
在全局样式中添加打印媒体查询,从样式层面强制canvas不参与打印渲染,避免JS变量控制失效导致的异常残留:
@media print { canvas[data-chart-canvas] { display: none !important; } }
对应给canvas节点加标识属性:<canvas ref={refChart} data-chart-canvas style={{ display: (printing) ? 'none' : 'block' }} width="100%" />
3. 调整Puppeteer导出逻辑,等待资源加载完成后再生成PDF
修改Puppeteer导出代码,增加自定义等待条件,确保所有图表图片都加载完成后再触发PDF生成:
// 等待所有图表图片生成并加载完成 await page.waitForFunction(() => { const chartImages = document.querySelectorAll('.chartImage'); return Array.from(chartImages).every(img => { return img.src.startsWith('data:image') && img.complete && img.naturalHeight > 0; }) }); // 生成PDF时开启相关适配参数 await page.pdf({ format: 'A4', printBackground: true, preferCSSPageSize: true, // 其他你的原有配置 })
内容的提问来源于stack exchange,提问作者Ricardo Gaefke
相关产品推荐
相关产品推荐

