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

如何通过Python Flask服务器下载HTML Canvas?空图问题排查

问题

我有一个包含两个Canvas组件和一个按钮的HTML页面,点击按钮时希望将两个Canvas合并为一张图片下载。我尝试了以下代码,但下载的图片为空(Canvas在浏览器中显示正常):

<head>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script>
        function downloadImages() {
            var canvas1 = document.getElementById('bar_canvas');
            var canvas2 = document.getElementById('pie_canvas');
            var context1 = canvas1.getContext('2d');
            var context2 = canvas2.getContext('2d');

            // Create images from the canvases
            var image1 = new Image();
            image1.src = canvas1.toDataURL('image/png');

            var image2 = new Image();
            image2.src = canvas2.toDataURL('image/png');

            // Create a new canvas to hold both images
            var mergedCanvas = document.createElement('canvas');
            mergedCanvas.width = canvas1.width + canvas2.width;
            mergedCanvas.height = canvas1.height;
            var context = mergedCanvas.getContext('2d');

            // Draw the two images onto the new canvas
            context.drawImage(image1, 0, 0);
            context.drawImage(image2, canvas1.width, 0);

            // Trigger the download
            var link = document.createElement('a');
            link.download = 'merged_image.png';
            link.href = mergedCanvas.toDataURL('image/png');
            link.click();
        }
    </script>
</head>
<body>
    <h1>Data Visualization</h1>
    <button onclick="downloadImages()">Download Images</button>
    <div style="display: flex; margin: auto;">
        <div style="width: 600px; height: 600px; margin-top: 200px; margin-left: 200px; ">
            <canvas id="bar_canvas"></canvas>

            <script>
                var old_data = {{ old_data|tojson }};
                var data = [];
                var labels = [];
                var bar_chart = document.getElementById('bar_canvas').toDataURL();

                Object.keys(old_data).forEach(function(key) {
                    labels.push(key);
                    data.push(old_data[key]);
                });

                var ctx = document.getElementById('bar_canvas').getContext('2d');
                var c1 = new Chart(ctx, {
                    type: 'bar',
                    data: {
                        labels: labels,
                        datasets: [{
                            label: "Data",
                            data: data,
                            backgroundColor: 'rgba(255, 99, 132, 0.2)',
                            borderColor: 'rgba(255, 99, 132, 1)',
                            borderWidth: 1
                        }]
                    },
                    options: {
                        scales: {
                            yAxes: [{
                                ticks: {
                                    beginAtZero: true
                                }
                            }]
                        }
                    }
                });
            </script>
        </div>

        <div style="width: 400px; height: 400px; margin-left: 100px; margin-top: 100px;">
            <canvas id="pie_canvas"></canvas>

            <script>
                var old_data = {{ old_data|tojson }};
                var data = [];
                var labels = [];

                Object.keys(old_data).forEach(function(key) {
                    labels.push(key);
                    data.push(old_data[key]);
                });

                var ctx = document.getElementById('pie_canvas').getContext('2d');
                var myChart = new Chart(ctx, {
                    type: 'pie',
                    data: {
                        labels: labels,
                        datasets: [{
                            label: 'Data',
                            data: data,
                            backgroundColor: 'rgba(255, 99, 132, 0.2)',
                            borderColor: 'rgba(255, 99, 132, 1)',
                            borderWidth: 1
                        }]
                    },
                    options: {
                        scales: {
                            yAxes: [{
                                ticks: {
                                    beginAtZero: true
                                }
                            }]
                        }
                    }
                });
            </script>
        </div>
    </div>
</body>

请问这是什么原因导致的?或者有没有更简便的方法来下载这两个图表?


问题原因

  • 图片加载异步未处理:你通过new Image()创建图片后直接调用drawImage,此时图片还没完成加载,无法绘制到合并Canvas上,最终生成的图片为空。
  • 冗余配置:饼图设置了yAxes刻度配置,饼图本身不需要该配置,虽不影响显示,但属于无效代码。

修复方案(两种方式)

方式一:直接绘制原Canvas内容(更简便)

不需要转成Image,直接把两个原Canvas的内容绘制到合并Canvas上,跳过图片加载的异步问题:

function downloadImages() {
    const canvas1 = document.getElementById('bar_canvas');
    const canvas2 = document.getElementById('pie_canvas');
    
    // 创建合并Canvas,取两个Canvas的最大高度避免内容被裁剪
    const mergedCanvas = document.createElement('canvas');
    mergedCanvas.height = Math.max(canvas1.height, canvas2.height);
    mergedCanvas.width = canvas1.width + canvas2.width;
    const ctx = mergedCanvas.getContext('2d');
    
    // 直接绘制原Canvas内容,饼图垂直居中显示
    ctx.drawImage(canvas1, 0, 0);
    ctx.drawImage(canvas2, canvas1.width, (mergedCanvas.height - canvas2.height)/2);
    
    // 触发下载
    const link = document.createElement('a');
    link.download = 'merged_image.png';
    link.href = mergedCanvas.toDataURL('image/png');
    link.click();
}

方式二:处理Image加载异步(适合需转图片的场景)

如果必须通过Image中转,需监听onload事件,确保图片加载完成后再绘制:

function downloadImages() {
    const canvas1 = document.getElementById('bar_canvas');
    const canvas2 = document.getElementById('pie_canvas');

    // 封装图片加载为Promise
    function loadImage(src) {
        return new Promise(resolve => {
            const img = new Image();
            img.onload = () => resolve(img);
            img.src = src;
        });
    }

    // 等待两张图片加载完成后合并
    Promise.all([
        loadImage(canvas1.toDataURL('image/png')),
        loadImage(canvas2.toDataURL('image/png'))
    ]).then(([image1, image2]) => {
        const mergedCanvas = document.createElement('canvas');
        mergedCanvas.height = Math.max(canvas1.height, canvas2.height);
        mergedCanvas.width = canvas1.width + canvas2.width;
        const ctx = mergedCanvas.getContext('2d');
        
        ctx.drawImage(image1, 0, 0);
        ctx.drawImage(image2, canvas1.width, (mergedCanvas.height - canvas2.height)/2);
        
        const link = document.createElement('a');
        link.download = 'merged_image.png';
        link.href = mergedCanvas.toDataURL('image/png');
        link.click();
    });
}

额外优化建议

  • 移除饼图配置中的scales.yAxes,修改为:
options: {}
  • 给Canvas设置固定宽高属性(如<canvas id="bar_canvas" width="600" height="600"></canvas>),避免Chart.js默认小尺寸Canvas被CSS拉伸导致模糊。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:22:32