メインコンテンツへスキップ

HugoでOGP画像を自動生成する

目次

こんにちは。

HugoでサムネイルとOGP画像(SNSでシェアしたときや Google Discover などで表示される画像)を自動生成する方法について紹介します。

↓こういうやつです

OGP画像のイメージ

環境
#

  • Hugo.Extended v0.160.0

  • Blowfish v2.103.0

OGP画像とは?
#

OGP画像は、SNSでシェアされたときに出てくる画像のことです。ZennとかQiitaにもこういうものがありますよね。

また、Google Discoverでもこの画像が使用されます。

画像があると目立ちますし、パッと情報が入ってきます。クリック数を増やすためには、画像はあったほうがいいと思います。

OGP画像の標準的なサイズは 1200x630px(縦横比 約1.91:1)とのことです。

なんでこんな比率なんでしょうか……?調べたところ、スマートフォンで見たとき表示スペースを埋めない程度に画像を置きたかったっぽいです。とにかく、16:9よりもさらに若干横長です。

背景の画像を作る
#

さて、ここからOGP画像を作っていきますが、まずは背景になる画像を作っていきましょう。

1200x630pxで作れば問題ないです。

私はCanvaで作りました。

OGPの背景画像
今回作ったOGP画像の背景

周りに色をつけて、真ん中は文字を置くスペースを空けています。

作り方としては、1200x630pxでキャンバスを作って、グラデーションの青い四角形の上に角丸をつけた白い長方形を乗せているだけです。

色のついた四角形にはグラデーションを付けるとおしゃれになる気がするのでおすすめです。

必要な素材を追加する
#

コードを書く前に、必要な素材を追加していきます。

OGP画像にはタイトルとサイトアイコン画像を付けますので、それらをassetsに追加しましょう。なお、外部サイトからフォントをダウンロードしてくることも可能です。

プロジェクトルートのassets/img/に、ogp-bg.pngという名前で先ほどの背景画像を追加し、favicon.pngという名前でこのサイトのアイコン画像を追加しました。

また、プロジェクトルートのassets/fonts/NotoSansJP-Medium.ttfを追加しました。フォントはGoogle Fontsなどからダウンロードするのがいいと思います。今回、バリアブルフォントでは太さが指定できなかったので、スタティックフォントを使いました。

Hugoに処理を追加
#

ではこれに文字や画像を乗せていきましょう。

プロジェクトルートのlayouts/partials/ogp-image.htmlを追加して、処理を書いていきます。アレンジして使ってください。

コードを載せますが、いろいろ工夫したので後ほど詳しく解説していきます。

ogp-image.html
{{/* OGP画像を生成しRelPermalinkを返す。生成不可の場合は空文字列 */}}
{{ $ogpImage := .Resources.Get "ogp.png" }}
{{ if not $ogpImage }}
  {{ $fontTitle := resources.Get "fonts/NotoSansJP-Medium.ttf" }}
  {{ $fontSub := resources.Get "fonts/NotoSansJP-Medium.ttf" }}
  {{ $baseImage := resources.Get "img/ogp-bg.png" }}
  {{ if and $fontTitle $fontSub $baseImage }}
    {{ if .IsPage }}

      {{/* タイトルの折り返し処理 */}}
      {{ $maxVisual := 32 }}
      {{ $titleText := "" }}
      {{ $lineWidth := 0 }}
      {{ $lineStart := 0 }}
      {{ $totalChars := strings.RuneCount .Title }}
      {{ $wordStart := 0 }}
      {{ $widthAtWordStart := 0 }}
      {{ $inAsciiWord := false }}
      {{ if gt $totalChars 0 }}
        {{ range $i := seq 0 (sub $totalChars 1) }}
          {{ $char := substr $.Title $i 1 }}
          {{ $isAscii := not (findRE "[^\x00-\x7F]" $char) }}
          {{ $isSpace := eq $char " " }}
          {{ $charWidth := 1 }}
          {{ if not $isAscii }}
            {{ $charWidth = 2 }}
          {{ end }}
          {{/* 英単語の開始を追跡(スペースまたは非ASCII→ASCIIの遷移で違う単語として扱う) */}}
          {{ if and $isAscii (not $isSpace) }}
            {{ if not $inAsciiWord }}
              {{ $wordStart = $i }}
              {{ $widthAtWordStart = $lineWidth }}
            {{ end }}
            {{ $inAsciiWord = true }}
          {{ else }}
            {{ $inAsciiWord = false }}
          {{ end }}
          {{ $newLineWidth := add $lineWidth $charWidth }}
          {{ if gt $newLineWidth $maxVisual }}
            {{ if and $inAsciiWord (gt $wordStart $lineStart) }}
              {{/* 英単語の前で改行 */}}
              {{ $line := substr $.Title $lineStart (sub $wordStart $lineStart) }}
              {{ $titleText = printf "%s%s\n" $titleText $line }}
              {{ $lineStart = $wordStart }}
              {{ $lineWidth = add (sub $lineWidth $widthAtWordStart) $charWidth }}
              {{ $widthAtWordStart = 0 }}
            {{ else }}
              {{/* 現在位置で改行 */}}
              {{ $line := substr $.Title $lineStart (sub $i $lineStart) }}
              {{ $titleText = printf "%s%s\n" $titleText $line }}
              {{ $lineStart = $i }}
              {{ $lineWidth = $charWidth }}
              {{ if $inAsciiWord }}
                {{ $wordStart = $i }}
                {{ $widthAtWordStart = 0 }}
              {{ end }}
            {{ end }}
          {{ else }}
            {{ $lineWidth = $newLineWidth }}
          {{ end }}
        {{ end }}
        {{ $lastLine := substr $.Title $lineStart (sub $totalChars $lineStart) }}
        {{ $titleText = printf "%s%s\n" $titleText $lastLine }}
      {{ end }}

      {{/* 中央にタイトル */}}
      {{ $articleTitle := images.Text $titleText (dict "font" $fontTitle "color" "#333333" "size" 60 "linespacing" 10 "x" 600 "y" 315 "alignx" "center" "aligny" "center") }}

      {{/* 右下にタグ(1つ目のみ) */}}
      {{ $tagText := "" }}
      {{ if .Params.tags }}
        {{ $tagText = printf "#%s" (index .Params.tags 0) }}
      {{ end }}
      {{ $tagArea := images.Text $tagText (dict "font" $fontSub "color" "#666666" "size" 30 "x" 1000 "y" 430 "alignx" "right") }}

      {{/* 左下にファビコンと著者名 */}}
      {{ $faviconFilter := "" }}
      {{ $faviconRaw := resources.Get "img/favicon.png" }}
      {{ if $faviconRaw }}
        {{ $favicon := $faviconRaw.Resize "96x96" }}
        {{ $faviconFilter = images.Overlay $favicon 120 445 }}
      {{ end }}
      {{ $authorName := site.Params.author.name | default "tamate" }}
      {{ $authorArea := images.Text $authorName (dict "font" $fontSub "color" "#666666" "size" 40 "x" 250 "y" 480) }}

      {{/* 右下にブログ名 */}}
      {{ $siteTitleArea := images.Text site.Title (dict "font" $fontSub "color" "#666666" "size" 40 "x" 1050 "y" 480 "alignx" "right") }}

      {{/* 画像を合成 */}}
      {{ if $faviconFilter }}
        {{ $ogpImage = ($baseImage | images.Filter $articleTitle $tagArea $authorArea $siteTitleArea $faviconFilter) }}
      {{ else }}
        {{ $ogpImage = ($baseImage | images.Filter $articleTitle $tagArea $authorArea $siteTitleArea) }}
      {{ end }}
    {{ else }}
      {{/* トップページ等はサイト名のみを中央に表示 */}}
      {{ $siteTitleLarge := images.Text site.Title (dict "font" $fontTitle "color" "#333333" "size" 80 "x" 600 "y" 315 "alignx" "center" "aligny" "center") }}
      {{ $ogpImage = ($baseImage | images.Filter $siteTitleLarge) }}
    {{ end }}
  {{ end }}
{{ end }}
{{ with $ogpImage }}{{ .RelPermalink }}{{ end -}}

そして、このコードを呼び出します。

どこからコードを呼び出すかはコードによって違います。head部分を書いているコードを探して呼び出すコードを追加しましょう。

_baseof.htmlなどにあることが多いです。

私が使っているblowfishというテーマの場合は、プロジェクトルートのlayouts/partials/head.htmlに書くようでした。themes/blowfish/layouts/partials/head.htmlをコピーして、layouts/partials/head.htmlとし、{{ template "_internal/twitter_cards.html" . }}の下に、以下のコードを書きます。

head.html
{{ $ogpImageURL := partialCached "ogp-image.html" . .RelPermalink | strings.TrimSpace }}
{{ if $ogpImageURL }}
  <meta property="og:image" content="{{ $ogpImageURL | absURL }}">
  <meta name="twitter:image" content="{{ $ogpImageURL | absURL }}">
  <meta name="twitter:card" content="summary_large_image">
{{ end }}

(生成できなかったときに固定画像を出す処理は省略しました)

ogp-image.htmlを読み込んで、取得したURLをog:imageとして出力しています。ついでにX(Twitter)用も一緒に出力しています。

生成したOGP画像はこんな感じになりました。

OGP画像

解説
#

全体の流れ
#

ページに対して個別にogp.pngがない場合に、OGP画像を生成することにしています。

タイトルは長くなることが想定されるので折り返し処理を入れて適宜改行し、その後ブログ名やブログアイコンの座標などを指定したうえで、これらを背景画像ogp-bg.pngと合成することでOGP画像を作っています。

ここまでの流れをogp-image.htmlとしてまとめておき、Webページの<head>部分の処理のときに呼び出しています。

全角・半角文字を考慮した文字の折り返し
#

{{/* タイトルの折り返し処理 */}}の部分について解説します。

1行にタイトルがすべて入るわけではないので、分割して複数行にしています。

文字数だけをもとに文章を折り返すと、全角文字と半角文字で幅が違うことによって見た目が悪くなってしまうので、全角半角を判断して全角文字を半角文字2文字分とすることでいい感じにしています。

余談

半角文字かどうかを判定している部分、文字コードが0x00~0x7Fかどうかで判断しているので、äとかéとかは全角の幅として処理されそう……まあいいか

英語の単語の禁則処理
#

英語は単語の途中で改行されると気持ち悪いので、スペースの位置を確認して改行するようにしています。

これがなかなか複雑で、コードが長くなっています……もっと楽な方法ないんですかね……?

文字とアイコンを表示
#

文字とアイコンを座標を指定して表示しています。記事のタイトルや設定された著者名を自動で取得して表示するようになっていますが、フォールバックの文字列がtamateなどとなっている場所がありますので、表示する文字列は適宜変更してください。

文字を表示する座標や大きさ、文字色なども変えられますので、カスタマイズしてみてください!

フォントは2種類(fontTitlefontSub)使えるようにしていますので、フォントを変えて使ってみてください。

Webフォントを使用するなら
#

Webフォントを使用する場合は、

{{ $fontTitle := resources.Get "fonts/NotoSansJP-Medium.ttf" }}

の部分を

{{ $fontTitle := resources.GetRemote "https://example.com/hogefont.ttf" }}

などとしてください。

OGP画像が生成されていることを確認する
#

hugo serverでサーバーを起動して、Webページを開きましょう。F12キーなどで開発者ツールを開き、<head>部分に<meta property="og:image" content="(URL)">があればOGP画像が生成されています。このURLを別タブで開くと生成された画像を見ることができます。

まとめ
#

ということで今回は、OGP画像を自動で作成する方法を紹介しました。

一度設定しておけばずっと自動でOGP画像が作られ、SNSで共有されたときの見た目が豪華になるのでおすすめです。

\ 魂こめて記事書いてます!よかったらシェアしてね! /

関連記事