サーバサイド(node.js)でhtmlとcssを使って画像を生成する


概要

サーバサイドで表やら装飾やらが入り組んだ画像を作りたい際にHTMLとCSSを使ってなんとかやれないものか調べたメモ書き
PuppeteerというCUIオンリーでもブラウザを操作できるNode.js製のライブラリが良さげ

ローカルにサーバーを用意

html+cssファイルを設置

Puppeteerを用いてブラウザで設置したhtmlへアクセス

スクショ取得

というのが大体の流れ

ローカルサーバー側

今回はXAMPPを使ってパパっと設置

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.3.1/sandstone/bootstrap.min.css">
    <style>
    .box{
        padding: 0.5em 1em;
        margin: 1em 0;
        background: #f4f4f4;
        border-left: solid 6px #5bb1b7;
        width: 500px;
    }
    .box p {
        margin: 0; 
        padding: 0;
    }
    </style>
</head>
<body>
    <div class="container">
        <div class="box target">
            <table class="table table-bordered">
                <tr>
                    <td>ほげほげ</td>
                    <td>ほげほげ</td>
                    <td>ほげほげ</td>
                </tr>
                <tr>
                    <td>ほげほげ</td>
                    <td>ほげほげ</td>
                    <td>ほげほげ</td>
                </tr>
                <tr>
                    <td>ほげほげ</td>
                    <td>ほげほげ</td>
                    <td>ほげほげ</td>
                </tr>
                <tr>
                    <td>ほげほげ</td>
                    <td>ほげほげ</td>
                    <td>ほげほげ</td>
                </tr>
            </table>
        </div>
    </div>
</body>
</html>

node.js側

事前準備として、Puppeteerをインストール

npm i puppeteer

以下JSファイルを作成

html_to_image.js
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // 倍率とか設定する
  await page.setViewport({width: 1920, height: 1080, deviceScaleFactor: 2})
  await page.goto('http://localhost/');
  // 対象のエレメントを指定
  let elm = await page.$('.target');
  await elm.screenshot({path: 'html_to_image.png'});
  await browser.close();
})();

実行

node html_to_image.js

結果

これだけでhtmlの特定エレメントを画像化することができた

メモ

  • ブラウザで閲覧するのとほぼ同じなのでbootswatchのCDNも普通に使えた
  • box-shadowとかflexboxとか使えるhtml/cssはやっぱり自由度高くて強力
  • 小さい要素だと画質が荒くなるので倍率高めで一旦作って後で調整するのもアリかも
  • ユーザー投稿型のサービスとかだとブラウザ側でcanvasに描画→画像化したものをアップロードという手も考えたけどフォントとかが環境依存になるしライセンスとかバリデーションとか考慮すると面倒くさそう
  • ブラウザ経由してるので処理はそれなりに重い。単純に作れる画像ならこれじゃなくGDとかこねくり回すか、実行頻度が高い場合はジョブキューとかで非同期にやった方が良い気がする

もっと良い方法あったらコメ欄で教えていただけると幸いです!