DEV Community

loading...

Save HTMLCanvasElements as images

Masui Masanori
Programmer, husband, father I love C#, TypeScript, etc.
・4 min read

Intro

This time, I want to save charts what are drawn by Chart.js as images.

To do this, I will try saving HTMLCanvasElement as an image.

In this time, I just use samples to use Chart.js.
I will try Chart.js next time.

Environments

  • Node.js ver.15.0.0

package.json

{
    "browserslist": [
        "last 2 version"
    ],
    "scripts": {
        "css": "npx postcss postcss -c postcss.config.js -d wwwroot/css -w"
    },
    "dependencies": {
        "autoprefixer": "^10.0.1",
        "chart.js": "^2.9.4",
        "postcss": "^8.1.2",
        "postcss-cli": "^8.1.0",
        "postcss-import": "^13.0.0",
        "precss": "^4.0.0",
        "ts-loader": "^8.0.6",
        "tsc": "^1.20150623.0",
        "typescript": "^4.0.3",
        "webpack": "^5.2.0",
        "webpack-cli": "^4.1.0"
    },
    "devDependencies": {
        "@types/chart.js": "^2.9.27",
        "whatwg-fetch": "^3.4.1"
    }
}
Enter fullscreen mode Exit fullscreen mode

Base projects

ChartPage.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <link rel="stylesheet" type="text/css" href="../css/chart_page.css" >
    </head>
    <body>
        <div id="outside">
            <h1>Chart</h1>
            <div class="chart_area">
                <canvas id="chart_1" class="chart_canvas"></canvas>
            </div>
        </div>
        <button onclick="Page.save()">Save</button>
        <script src="../js/chartPage.bundle.js"></script>
        <script>
            (function() {
                Page.init();
            })();
        </script>
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

chart_page.css

.chart_area{
    background-color: white;
    border: solid black 1px;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 300px;
    width: 300px;
}
.chart_canvas {
}
Enter fullscreen mode Exit fullscreen mode

Draw chart samples

This time, I use the chart sample in Chart.js document.

chart.page.ts

import { SampleDrawer } from "./charts/sample-drawer";

export function init() {
    const target = document.getElementById('chart_1');
    if (target == null)
    {
        return;
    }
    const drawer = new SampleDrawer();
    drawer.draw(target as HTMLCanvasElement);
}
Enter fullscreen mode Exit fullscreen mode

sample-drawer.ts

import Chart from "chart.js";

export class SampleDrawer {
    public draw(canvas: HTMLCanvasElement) {
        const context = canvas.getContext('2d');
        if (context == null) {
            console.error('failed getting context');
            return;
        }
        this.drawSample(canvas);
    }
    private drawSample(canvas: HTMLCanvasElement) {
        const chart = new Chart(canvas, {
            // as same as the sample script at "Creating a Chart"
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Result

Alt Text

The chart size comes from the parent element's(<div class="chart_area">) width or height.
Though I change the canvas size, background-color, and etc. before instantiating the chart instance, they will be ignored.

Save as a image

The HTMLCanvasElement can generate Blob or Base64 string and I can use them to create images.
If I save image by a server application, I prefer using "toDataURL" and send Base64 string to the server.

But this time, I use "toBlob()" to download by TypeScript.

chart.page.ts

...
export function save() {
    const target = document.getElementById('chart_1');
    if (target == null)
    {
        console.error('chart_1 was not found');
        return;
    }
    const targetCanvas = target as HTMLCanvasElement;
    targetCanvas.toBlob((blob) => downloadImageBlob(blob), 'image/jpeg', 1);
}
function downloadImageBlob(blob: Blob|null) {
    if(blob == null){
        console.error('Failed getting image blob');
        return;
    }
    if (navigator.msSaveBlob == null) {
        if (navigator.msSaveBlob == null) {
        const linkElement = document.getElementById('download_link') as HTMLAnchorElement;
        linkElement.href = window.URL.createObjectURL(blob);
        linkElement.download = 'sample_file.jpg';
        linkElement.click();
        return;
    }
    // for IE
    window.navigator.msSaveBlob(blob, 'sample_file.jpg');
}
Enter fullscreen mode Exit fullscreen mode

ChartPage.html

...
        <button onclick="Page.save()">Save</button>
        <a id="download_link"></a>
        <script src="../js/polyfill/toBlob.js"></script>
        <script src="../js/chartPage.bundle.js"></script>
...
Enter fullscreen mode Exit fullscreen mode

For downloading created image, I add an AnchorElement.

And For IE, I add a polyfill file.

Result

Alt Text

Where did the background color come from?
It's because the chart didn't have background colors, and I saved as Jpeg image(no transparency).

So I have to set background color.
But it ignores canvas background color.

Set background color

To set background color, I use plugins of Chart.js.

sample-drawer.ts

    public draw(canvas: HTMLCanvasElement) {
        const context = canvas.getContext('2d');
        if (context == null) {
            return;
        }
        this.drawBackground();
        this.drawSample(canvas);
    }
    private drawBackground() {
        Chart.pluginService.register({
            beforeDraw: c => {
                const ctx = c.ctx;
                if (ctx == null){
                    return;
                }
                const canvas = c.canvas;
                if (canvas == null) {
                    return;
                }
                ctx.fillStyle = 'rgba(255, 255, 255, 1)';                
                ctx.fillRect(0, 0, canvas.width, canvas.height);
            }
        });
    }
...
Enter fullscreen mode Exit fullscreen mode

Result

Alt Text

Hide chart and save image

Can I only want to show an image what is created from a chart dynamically and hide the chart?

display:none; (Failed)

chart_page.css

#outside{
    display: none;
}
...
Enter fullscreen mode Exit fullscreen mode

I couldn't get the blob by "toBlob()".

visibility: hidden; (Success)

chart_page.css

#outside{
    visibility: hidden;
}
...
Enter fullscreen mode Exit fullscreen mode

Draw outside (Success)

#outside{
}
.chart_area{
...
    height: 1024px;
    width: 1024px;
    position: absolute;
    top: -2000px;
    left: -2000px;
}
...
Enter fullscreen mode Exit fullscreen mode

Discussion (0)