🖼️

Hono と Satori で動的 OGP 画像生成を遊び倒す

に公開

なぜこの遊びを始めたか

みなさん、ウェブページの OGP (Open Graph Protocol) 画像、どうしていますか?
静的な画像を事前に用意しておくのが一般的だと思いますが、今回は以前の@syumai さんのアイコンの色が変わるOGPのポストに触発されて「他にも何か面白いことができないか?」と考えOGPで遊んでみました!
https://github.com/ponyo877/ogp-playground

Cloudflare Workers というエッジ環境で、HonoSatoriを使い、リクエストに応じてリアルタイムに OGP 画像を生成する、そんな遊びをしてみました。
この記事では、その「遊び」のいくつかをご紹介します。

遊び一覧 (OGP 実例)

実際にどんな OGP 画像が生成できるのか、いくつか見ていきましょう。

1. timestamp OGP

アクセスした瞬間の日時を表示する OGP 画像です。
moment-timezone を使って日本標準時をRFC3339で取得します。
フレーム画像は事前にデザインされた枠線や背景を持つ画像を Base64 エンコードした Data URI として用意し、JSX 内で <img> タグの src に指定することで、背景として表示しています。これにより、複雑な装飾も簡単に適用できます。
(この記事のOGP画像に使っているフレームは私はMacのKeyNoteでせこせこ作りました。)

表示例: (画像はアクセス毎に変わります)
https://ogp-playground.ponyo877.workers.dev/timestamp
https://ogp-playground.ponyo877.workers.dev/timestamp

2. UUID/ULID OGP

アクセスするたびに新しい UUID (v4) または ULID を生成して表示します。

表示例:
https://ogp-playground.ponyo877.workers.dev/uuid
https://ogp-playground.ponyo877.workers.dev/uuid

https://ogp-playground.ponyo877.workers.dev/img/ulid
https://ogp-playground.ponyo877.workers.dev/ulid

3. 今日の運勢 OGP

アクセスするとランダムで今日の運勢 (大吉〜大凶) が表示されます。
運勢の画像はChatGPTで画像生成の精度が飛躍した時の@tokorotenさんの運勢占いgifを作るポストを参考に生成しました

表示例:
https://ogp-playground.ponyo877.workers.dev/fortune
https://ogp-playground.ponyo877.workers.dev/fortune

技術的なポイント:

  • timestampやUUID/ULIDはアクセスのタイミングで新しい画像を生成していますが、今日の運勢 OGPは少し特殊で、ogp-playground 自体では画像を生成せず、事前に今日の運勢 (大吉〜大凶)画像を生成しておき、OGP
  • src/index.tsx 内で運勢 (文字列) をランダムに決定し、その運勢に対応する外部の静的画像 URL (https://ogp-playground.folks-chat.com/fortune/${fortune}.png) へリダイレクトしています。Cloudflare R2で配信しています
  • リダイレクトは動的生成のデモとしては少し反則かもしれませんが、こういう使い方もできるという一例です...
src/index.tsx (/fortune 部分)
    case 'fortune':
      const fortunes = ['daikichi', 'kichi', 'shoukichi', 'suekichi', 'kyou', 'daikyou'];
      const fortune = fortunes[Math.floor(Math.random() * fortunes.length)];
      // HTMLを返すエンドポイント (/fortune)
      imageURL = `${endpoint}/img/fortune?f=${encodeURIComponent(fortune)}`;
      return c.text(renderHtml(key, imageURL, ''), /* ... */)
src/index.tsx (/img/fortune 部分)
    const fortune = c.req.query('f') || '<BLANK>';
    // 外部の画像URLへリダイレクト
    return c.redirect(`https://ogp-playground.folks-chat.com/fortune/${fortune}.png`)

4. 今日の四字熟語 OGP

アクセスするたびにランダムな四字熟語とその意味を表示します。
(OGP画像を表示するだけで、URLにアクセスしても四字熟語の解説は見れません...😀)

表示例:
https://ogp-playground.ponyo877.workers.dev/yojijukugo
https://ogp-playground.ponyo877.workers.dev/yojijukugo

技術的なポイント:

  • 事前に漢字ペディアから四字熟語をスクレイピングで取得しておきます。
    https://www.kanjipedia.jp/sakuin/yojijyukugo/あ
  • Flexbox を使って「読み」「四字熟語」「意味」をレイアウトしています。
  • 「意味」のフォントサイズ調整: 意味の部分は文章の長さが短いものも長いものもあるので指定された高さと幅に収まるようにフォントサイズを二分探索で計算しています。
src/lib/img.tsx
// 画像生成関数
async function calculateFontSize(
  text: string,
  minFontSize: number,
  maxFontSize: number,
  heightLimit: number,
  targetWidth: number,
  fontData: ArrayBuffer,
  fontOptions: FontOptions,
): Promise<number> {
  let low = minFontSize;
  let high = maxFontSize;
  let bestFitFontSize = minFontSize;

  while (low <= high) {
    const mid = Math.floor((low + high) / 2);
    console.log('low:', low, 'high:', high, 'mid:', mid);
    const heightAtMidFont = await calculateTextHeight(text, targetWidth, mid, fontData, fontOptions);
    if (heightAtMidFont <= heightLimit) {
      bestFitFontSize = mid;
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }

  return bestFitFontSize;
}

5. ランダム Wikipedia OGP

アクセスするたびに Wikipedia からランダムな記事のタイトルを取得して表示します。
(OGP画像を表示するだけで、URLにアクセスしてもWikiのページには飛べません...😀)

以下にアクセスすると日本語のランダムなWikiのページに飛ぶのを利用しています
ランダムWikiの遷移先のheaderにあるページタイトルを取得してOGP画像に記載しています
http://ja.wikipedia.org/wiki/Special:Randompage

表示例:
https://ogp-playground.ponyo877.workers.dev/wiki
https://ogp-playground.ponyo877.workers.dev/wiki?x

まとめ

Cloudflare Workers, Hono, Satori を組み合わせることで、エッジ環境で動的に OGP 画像を生成する面白い遊びができました。

  • JSX でデザインを記述できる Satori の表現力
  • WASM 化された Resvg/Yoga による Workers 環境での画像生成/レイアウト
  • テキストサイズに応じた二分探索による動的なフォントサイズ調整

など、技術的にも興味深い要素がたくさん詰まっています。

今回紹介した以外にも、アイデア次第で様々な動的 OGP が考えられそうです。
小さな改善案で言うと四字熟語のやつやWikiのやつは実際にOGPに表示されているページに遷移されればより良さそうです(私の技術力では無理でした...)

皆さんもぜひ、動的 OGP 生成の世界を探求してみてください!!!

Discussion