かぁくん本紀自作CMS・自作サーバーにて運用中
ブログ・ホームページ >

JavascriptでHTMLをPNG画像化する

公開日時: 2022-10-20 03:20:49更新日時: 2022-10-20 21:00:05JavascriptでHTML中の特定の要素の内容をスクリーンショットのように丸ごとPNG画像化してダウンロードさせる処理を書く機会があったので、ここでも共有。いつも通り著作権放棄しますのでご自由にどうぞ。

Javascriptの仕様上、IMGタグやCSSのbackground-imageはファイルをBASE64で埋め込んだものしかダウンロード画像に含められませんが、テキストの装飾や背景色は問題なく画像化できます。

(まずはコードを貼っておいて、使い方はページ下部に記載しました。)


class elm2img {
    constructor (w, h) {
        this.canv = document.createElement("canvas");
        this.set_size(w, h)
        
        this.ctx = this.canv.getContext("2d");
        
        this.css_code = "";
    }
    
    set_size (w, h) {
        this.canv.width = w;
        this.canv.height = h;
        
        this.width = w;
        this.height = h;
    }
    
    add_css_code (code) {
        this.css_code += "<style>" + code + "</style>";
    }
    
    get_image_url (elm) {
        this.ctx.clearRect(0, 0, this.width, this.height);
        
        var xml_serializer = new XMLSerializer();
        var svg_code = "<svg xmlns='http://www.w3.org/2000/svg' width='" + this.width + "' height='" + this.height + "'>" + this.css_code + "<foreignObject width='100%' height='100%'>" + xml_serializer.serializeToString(elm) + "</foreignObject></svg>";
        
        return "data:image/svg+xml;charset=utf-8;base64," + btoa(encodeURIComponent(svg_code).replace(/%([0-9A-F][0-9A-F])/g, function (match, match_1) {
                return String.fromCharCode('0x' + match_1);
        }));
    }
    
    save_png (elm, file_name) {
        var img_elm = document.createElement("img");
        
        var canv = this.canv;
        var ctx = this.ctx;
        
        img_elm.onload = function () {
            ctx.drawImage(img_elm, 0, 0);
            
            var a_elm = document.createElement("a");
            a_elm.href = canv.toDataURL("image/png");
            a_elm.download = file_name;
            a_elm.click();
        };
        
        img_elm.src = this.get_image_url(elm);
    }
}

使い方

まず、ダウンロードさせる画像のサイズを指定してクラスを初期化します。
    e2i = new elm2img(600, 400);

次に、画像化する要素に絡んでくるスタイルシートを全て文字列として登録します。(ここで登録しなかったスタイルは画像化時に無視されます)
    e2i.add_css_code(document.getElementsByTagName("style")[0].innerText);
    e2i.add_css_code(" h1 { color: #ff0000; border: 1px solid #333333; }");

最後に、画像化する要素とダウンロード時のファイル名を指定して画像化を実行します。
    e2i.save_png(document.getElementById("example"), "画像.png");

仕組み

他にやりようがないからではありますが、めちゃくちゃ回りくどいことやってます。

まず、JSにCANVAS要素を画像化する機能があることを利用します(canvas.toDataURL)。

このCANVASにはIMGタグを貼り付けられることを利用して、btoaでBASE64エンコードしたSVGをIMGタグのsrcに指定して無理やり貼り付けることにします(context.drawImage)。

SVGはXMLなので、foreignObjectタグを活用して、別途生成されたXMLコードを埋め込むことができます。

で、このXMLコードとして、目的の要素の内容をXML化したものを指定すれば、というわけです(XMLSerializer.serializeToString)。

ちなみに、btoaは文字列をバイト数に関わらず1文字単位で認識するわりに、内部では1バイト単位で処理する前提になっているなどというよくわからない実装なので、マルチバイト文字が入っているとエラーになります。
この回避策として、今回のスクリプトではXMLコードを一旦encodeURIComponentでパーセントエンコードしてから、1バイトごとにデコードして"文字化けしたASCII文字列"として認識させることで強引にエラーを回避しています。


まあ、例えば、CANVASに最初からHTMLを貼り付けるような機能でもあればこんな回りくどいことをする必要はないんでしょうけど、普通はそういう需要なんかないこともあって仕様化されてないのかなぁ……
この記事のタグ:
Javascript