ブラウザで完結する画像ジェネレーターで発信画像の作成を効率化する

画像ジェネレーターで作成した本記事のメイン画像

こんにちは、RightTouchでデザインエンジニアをしている @kan_dai です。

今回は、ブログ記事のメイン画像やビデオ会議の背景画像をブラウザ上で作れる社内ツールを作った話を紹介します。

作ったもの

エンジニアブログの記事の先頭には、タイトルと著者名が入ったメイン画像を毎回用意しています。同じようにブランドイメージに合った画像を用意している会社も多いのではないでしょうか。

この画像を、ブラウザ上で内容を入力して表示されているそのままの見た目でダウンロードできるツールを作りました!

ブログメイン画像の編集画面。プレビューと、タイトルや著者情報の入力フォームが並ぶ

ブログメイン画像の編集画面

この記事のメイン画像も、このツールを使って作成しました!

タイトルと著者名を入力して、アイコンと背景を選べば完成です。簡単!

選ぶと言っても背景は現在1種類ですが、アドベントカレンダーのときには違う背景を用意するなど、追加する予定があったのであらかじめ選択できるように作成しています。

プレビューを見ながら編集できるので、タイトルが長いときの改行位置や文字サイズの調整もその場で確認できます。

エンジニアブログ用に作り始めたのですが、noteなどエンジニアブログ以外の発信でも使えるように育てています。

解決したかった課題

これまで記事のメイン画像は、Figmaのテンプレートから作っていました。

そのため、記事を書いた人がFigmaで作業できる場合はまだ良いのですが、そうでない場合はFigmaを使える人に依頼する必要があり、次のような手間がありました。

  • 書いた本人がすぐに画像を用意できず、依頼と受け渡しのやり取りが発生する
  • テンプレートの複製やテキストの差し替え、書き出しといった作業が依頼された側にも毎回発生する
  • タイトルの文字数や改行の調整は見た目を見ながら決めたいのに、依頼ベースだと往復が発生する

誰でもブラウザで完成形を確認しながら自分で作れるようにすれば、この手間がまるごとなくなります。

作る側も依頼される側も、できるだけ楽をしたいというのが出発点でした。

同じ構造の課題も解決できるように

また、ビデオ会議の背景画像にも、フォーマットはあるものの、入社した人ごとに名前を差し替えた画像を誰かが作る必要があるという同じ構造の課題がありました。

こちらも同じ仕組みで解決できそうだったので、このツール内で作成できるようにしました。

ビデオ会議背景の編集画面。会社ロゴ入りの背景に肩書と名前が表示されている

ビデオ会議背景も同じ操作感で作れる

メンバーが増えるほど作成の回数も増えるので、ツール化のレバレッジが効きやすかったと思います。

技術構成

React + Vite + TypeScriptで作った静的なSPAを、Cloudflare Pagesに配置しています。

画像の生成はすべてクライアントサイドで完結していて、サーバーでの描画処理はありません。

SnapDOMによる「見たまま = 出力」

画像化には SnapDOM というライブラリを利用しました。

プレビューとして表示している実寸のDOMノードをそのままキャプチャする方式なので、プレビューで見ている見た目のまま出力されるのがポイントです。

snapdom.dev

HTMLから画像を生成するライブラリとしては、Vercel社が開発している Satori も有名で候補として検討していました。

ただ、Satoriは実際のDOMをキャプチャするのではなく、JSXで書いた定義を限定的なHTML/CSSサブセットの範囲でSVGに描画する方式です。

そのため、公式のREADMEでも「ブラウザ描画と100%一致することは保証しない」と明記されています。

Also, Satori does not guarantee that the SVG will 100% match the browser-rendered HTML output since Satori implements its own layout engine based on the SVG 1.1 spec.

今回は、ブラウザで表示しているものをそのまま画像にしたかったので、SnapDOMを採用することにしました。

テンプレートは実寸(たとえばブログ画像なら1280×670px)のJSXとして描画し、プレビューでは transform: scale で画面に収まるよう縮小表示しています。キャプチャ対象のノード自体は実寸のままなので、SnapDOMはそれをそのままの解像度で画像化できます。

Webフォントでどの環境でも同じ見た目に

テンプレートの書体にはWebフォント(Noto Sans JP)を使い、SnapDOMの embedFonts: true でキャプチャ結果に埋め込んでいます。

OSにインストールされたフォントに頼ると、作成者のOSやブラウザによって字形や行の折り返しが変わってしまうので、Webフォントにして誰がどの環境で作っても同じ見た目の画像になるようにしました。

初回キャプチャで文字が欠ける問題

ただ、このWebフォントに1つハマりどころがあり、フォントキャッシュの冷えた初回キャプチャでは、日本語フォントのデコードがラスタライズに間に合わずに文字の欠けた画像ができてしまうことがありました。

そこで、キャプチャ結果を縮小したピクセル列を比較し、連続2回の結果が一致するまでキャプチャをやり直す処理を入れています。

描画が安定した結果だけをダウンロードさせることで、文字欠けの問題を回避しました。

async function captureStable(node, options) {
  let prev = null
  let result = await snapdom(node, options)
  for (let attempt = 0; attempt < 6; attempt++) {
    const fp = await rasterFingerprint(result) // 縮小したピクセル列
    if (prev && isSameRaster(prev, fp)) break  // 2回一致したら安定とみなす
    prev = fp
    await new Promise((r) => setTimeout(r, 350))
    result = await snapdom(node, options)
  }
  return result
}

工夫したこと

メンバープリセットで入力の手間を減らす

著者名の入力欄はメンバーの検索を兼ねていて、候補から選ぶとローマ字表記とアイコンもまとめて入力されます。

ビデオ会議背景でも同様に、メンバーを選ぶだけで肩書・名前・ローマ字が埋まります。

プリセットのリストを表示

プリセットのリストをサンプルのデータで表示している画面

このプリセットの元になるメンバーデータをどう用意するかが問題で、手作業でデータを更新する運用だと、入社や退職のたびにメンテナンスが発生し、いずれ実態とずれていくことが予想されます

そこで、社内で運用されているGoogleスプレッドシートのメンバーリストを取得元にしました。このリストは基本的に最新に保たれているので、ツール側でデータを管理する必要がありません。

アイコンはリストのメールアドレスを使ってSlack APIでプロフィール画像を取得しています。Slackのアイコンと発信用のアイコンは分けたいという場合のために、アップロードもできるようにしています。

データ更新の自動化とGitHub Actionsの落とし穴

このデータ取得を定期実行する仕組みとして、最初はGitHub ActionsでスプレッドシートとSlackからデータを取得し、生成結果をコミットしてCloudflare Pagesにデプロイする構成を考えていました。

ただ、この構成には以下のような問題がありました。

  • 生成結果の反映にPRの作成とマージという作業が挟まる
  • メンバーの名前やアイコンといった情報がGitのリポジトリに残ってしまう

そこで、GitHub ActionsはCloudflare PagesのDeploy Hookを毎朝実行するだけにして、データの取得からビルドまでをCloudflare Pagesのビルド内で完結させる構成にしました。

メンバーデータ更新の流れ。GitHub ActionsがDeploy Hookを実行し、Cloudflare Pagesのビルド内でスプレッドシートとSlackからデータを取得して静的サイトを生成する

メンバーデータ更新の流れ

ビルドコマンドの先頭でメンバー同期スクリプトが走り、スプレッドシートとSlackから取得したデータでプリセットのTypeScriptファイルとアバター画像を生成してから、Viteのビルドが実行されます。

生成したデータはビルド成果物の dist にだけ含まれるので、リポジトリには個人情報が残りません。

認証はシンプルに

社内ツールとはいえ、メンバーの名前とアイコンを含むサイトなので認証を追加することにしました。

ただ、複雑な構成にはしたくなかったので、Cloudflare Pages Functionsで共有パスワード1つだけのゲートを置きました。

Cloudflare Pages Functionsを利用した共有パスワード1つだけの認証画面

共有パスワード1つだけの認証画面

パスワードはパスワード管理ツールに置いておき、社内のメンバーであれば誰でも入れるようにしました。

入力内容はローカルストレージに保存

タイトルや著者名などの入力内容は、ローカルストレージへ保存して次回アクセス時に復元しています。

自分の記事用の画像を作る場合、著者名もアイコンも前回と同じなので、本当にタイトルを変えるだけで次の記事の画像が完成します

繰り返し使うツールではこういった工夫がUXに繋がるのではないかと考えています!

作ってみてどうだったか

自分がエンジニアブログの運営に関わっていることもあり、自分で使うことも多いですがとても便利です!やはり自分がユーザーとなるツールだと自分で使い勝手を試しながら作っていけるのが良いですね。

嬉しかったのは、広報の方も実際に使ってくれて、とても楽だったと言ってくれたことです!

実際に使ってもらっての感想

実際に使ってもらったうえで改善のフィードバックまでもらえるのは、社内ツールとしてありがたい限りです!

まとめ

「Figmaで作業できる人に依頼しないと画像が作れない」という小さなボトルネックを、ブラウザで完結するツールにして解消した話でした。

振り返ってみると、ツール本体の画像生成よりも、メンバーデータの鮮度を保ったまま自動で反映する仕組みのほうに工夫が詰まっています。

社内ツールは作ること自体より、メンテナンスし続けなくても使える状態を保つことのほうが難しいので、運用の手間を最初に潰しておくのは良かったと感じています。

同じような課題を抱えている方の参考になれば嬉しいです!

採用情報

RightTouchでは、Product Engineerをはじめ、プロダクト価値を一緒に育てる仲間を積極採用中です。カジュアル面談も歓迎しています。ご興味があれば、ぜひ採用ページをご覧ください。

開発体験や社内の小さな課題解決にも手を動かせる環境に興味のある方は、ぜひお気軽にお声がけください!

righttouch.co.jp

righttouch.co.jp

speakerdeck.com