こんにちは。野崎です。
前回は、imgixの入門記事「pictureタグでレスポンシブ対応を行う」を書かせていただきました。
https://blog.microcms.io/imgix-picture/
今回は少し発展させて、imgixを用いて動的に画像を生成する方法を紹介します。この手法を用いることで、動的なOGP画像を生成することができます。
なお今回はSSGフレームワークとしてNext.jsを使用します。
Next.jsの使用方法について詳しく知りたい方は、以下の記事をご覧ください。
https://blog.microcms.io/microcms-next-jamstack-blog/
https://blog.microcms.io/next-pagination/
フレームワークに依存する部分はほとんどありませんので、Next.js以外のフレームワークにも転用できるかと思います。
動的OGP画像ってなに?
そもそも動的な画像と言われてもピンとこない方もいらっしゃるかもしれません。動的な画像とはそのコンテンツ(記事など)やデータに応じて自動で生成される画像をさします。
今回は各ページのタイトル、著者に応じて動的に画像を生成する方法を紹介します。
例えばタイトルが「Imgixで動的に画像・OGP画像を生成する(これはサンプルです)」で著者が「りゅーそう」である場合以下のような画像を生成します。
この画像をmicroCMS経由でimgixAPIを用いて生成してみましょう。
imgixAPIについて
microCMSでは画像管理をimgixを経由して行なっています。ですので、microCMSにアップロードした画像にもimgixのAPIを用いることが可能です。
基本的なAPIはドキュメントをご覧ください(一部非対応のAPIがあります)。
https://document.microcms.io/image-api/introduction
今回はimgixのTypeSettingというAPIを使用します。以下のチュートリアルが参考になります。
https://docs.imgix.com/tutorials/multiline-text-overlays-typesetting-endpoint
(画像引用: https://docs.imgix.com/tutorials/multiline-text-overlays-typesetting-endpoint より)
元となる画像に~text
というエンドポイントを用いて画像を重ねます。〜text
をTypeSetting Endpointと呼びます。~text
にはblend
とmark
という2つの文字や画像を重ねるためのimageURLを配置するエンドポイントがあり、これらを最大限に利用して動的に画像を生成します。
今回の画像に当てはめると以下のようになります。blend
とmark
で2つの要素を表現します。それに加えてユーザーネームも動的に生成したいので、text
というパラメータも駆使して生成します。
microCMS経由でimgixAPIを使用する
画像を生成するためのmicroCMSの設定を行います。
まずは動的な画像を生成するためのコンテンツAPIを作成します。今回はブログ記事の画像を生成することを想定して以下のようなAPIを作成します。
ここでは、
- title(記事のタイトル)
- author(著者の情報)
- image(サムネイルで使用する画像)
を最低限APIスキーマに設定します(記事本文を書くためのリッチエディタなど適宜追加してください)。
titleが画像中央に表示されるタイトル、imageは背景画像、authorは参照フィールドです。
また、この参照されたauthorというコンテンツAPIは以下のようなスキーマになります。
- name(著者名)
- image(著者の画像)
この情報は画像の右上に配置される情報になります。
ではそれぞれのコンテンツAPIにコンテンツを追加していきましょう。
まずは著者情報を作成します。著者名と著者画像を記入してください。
本体のブログAPIを作成します。先ほど作成した著者情報を持ったAPIを追加し、ブログAPIの画像には背景の元となる画像をアップロードしてください。
microCMS側の準備は以上になります。
今回の記事で生成される画像を再掲します。
このようにmicroCMSを用いると、完成するUIをイメージしながらAPIスキーマを設定することができます。
imgixAPIを用いて、画像を生成する
まずはimgixAPIを用いて画像を生成してみましょう。今回はSSGフレームワークであるNext.js(TypeScript)を使用します。
Next.jsで以下のようなコンポーネントを作成します。
//components/CustomImage.tsx
import base64url from 'base64url';
type Author = {
id: string;
name: string;
image: {
url: string;
};
};
type Props = {
baseImageUrl: string;
title: string;
author?: Author;
width?: number;
height?: number;
className?: string;
};
export const CustomImage: React.VFC<Props> = ({
baseImageUrl,
title,
author,
width = 1200,
height = 630,
className,
}) => {
return (
<picture className={className}>
<source
srcSet={`${baseImageUrl}?w=${width}&h=${height}}`}
media="(min-width: 1400px)"
/>
<source
srcSet={`${baseImageUrl}?w=${width * 0.8}&h=${height * 0.8}}`}
media="(min-width: 1280px)"
/>
<source
srcSet={`${baseImageUrl}?w=${width * 0.6}&h=${height * 0.6}}`}
media="(min-width: 960px)"
/>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={`${baseImageUrl}?w=${width * 0.55}&h=${height * 0.55}}`}
alt={title}
/>
</picture>
);
};
前回の記事で紹介させていただいたようにmicroCMSのURLにimgixのパラメータを付与することで画像の加工を行っています。また、sourceタグでwidthとheightの値を出し分けを行うことでレスポンスデザインにも対応します(値はあくまで一例です)。
この記事をNext.jsのpagesで使用します。
pages/blogs/index.tsx
export default function Home(blogs): {
//getStaticPropsなどでmicroCMSでblogsデータを受け取り表示する。
return (
<section>
{blogs.map((content) => (
<Fragment key={content.id}>
<article>
<Link href={`/blogs/${content.id}`} passHref>
<a
href={`/blogs/${content.id}`}
>
<div>
{content?.author?.map((author) => {
return (
<Fragment key={author.id}>
<CustomImage
baseImageUrl={content.image.url}
width={600}
height={315}
title={content?.title}
author={author}
/>
</Fragment>
);
})}
</div>
<div>
<h3>
{content.title}
</h3>
</a>
</Link>
</article>
</Fragment>
))}
</section>
);
ここまでで、以下のように元画像が画面上に表示されるかと思います。
これにタイトルなどの情報をつけていきます。先ほどのCustomImageコンポーネントにパラメーターを付与していきます。
一部抜粋します。以下のように先ほどのsrcSetにimgixAPIのパラメーターを付与していくことで画像を加工することが可能です。
<source
srcSet={`${baseImageUrl}?w=${width}&h=${height}}&txt64=${base64url(
author.name,
)}&txt-pad=56&txt-color=00695C&txt-size=16&txt-align=left,top&mark64=${base64url(
`${author.image.url}?w=30&h=30`,
)}&mark-x=${20}&mark-y=${46}&blend64=${base64url(
`https://assets.imgix.net/~text?txtsize=24&txt-color=009688&w=${
width - 80
}&txt-align=middle&txtfont=Hiragino%20Sans%20W6&txt-track=2&txt64=${base64url(
title,
)}`,
)}&blend-mode=normal&blend-align=top,left&blend-x=40&blend-y=100`}
media="(min-width: 1400px)"
/>
まずはauthor(著者情報)のパラメーターを見ていきます。
imgixAPIパラメーターはこちらを参照ください。
&txt64=${base64url(author.name,)}&txt-pad=56&txt-color=00695C&txt-size=16&txt-align=left,top
txt64
の64とはBase64に基づくURLセーフな値を指します。詳しくはimgixのこちらの記事をご覧ください。
ここでは以下のライブラリを用いて、実装をしています。
https://www.npmjs.com/package/base64url
import base64url from 'base64url';
author情報をpropsとして受け取って、表示をしています。text
パラメーターは拡張性は多くありませんが、paggingやtextSizeなどを設定することができます。
次にaurhor(著者情報)の画像を表示するパラメーターです。画像の上にさらに画像を重ねたい場合はmark
パラメーターを用いることができます。
imgixAPIパラメーターはこちらを参照ください。
&mark64=${base64url(`${author.image.url}?w=30&h=30`)}&mark-x=${20}&mark-y=${46}
先ほどと同じようにbase64エンコードをして画像を表示します。mark
は位置をさらに細かく調整することができます。
次にタイトルを表示するパラメーターです。blend
というパラメーターを使用します。
imgixAPIパラメーターはこちらを参照ください。
&blend64=${base64url(`https://assets.imgix.net/~text?txtsize=24&txt-color=009688&w=${width - 80}
&txt-align=middle&txtfont=Hiragino%20Sans%20W6&txt-track=2&txt64=${base64url(title)}`)}
&blend-mode=normal&blend-align=top,left&blend-x=40&blend-y=100
blend
もmark
と同様にパラメーターを付与することで、細かくカスタマイズすることができます。タイトルに日本語を含める場合にはHiraginoSans
を指定することに注意してください。またblend-mode
を指定することによって、画像を重ねることができます。
今回書いたコードの全容はこちらになります。デバイスサイズによって、画像の大きさ・それに合わせて文字の大きさなどを動的に生成しています。これによって動的画像であってもパフォーマンスの向上が期待できます。
import base64url from 'base64url';
type Author = {
id: string;
name: string;
image: {
url: string;
};
};
type Props = {
baseImageUrl: string;
title: string;
author?: Author;
width?: number;
height?: number;
className?: string;
};
export const CustomImage: React.VFC<Props> = ({
baseImageUrl,
title,
author,
width = 1200,
height = 630,
className,
}) => {
return (
<picture className={className}>
<source
srcSet={`${baseImageUrl}?w=${width}&h=${height}}&txt64=${base64url(
author.name,
)}&txt-pad=56&txt-color=00695C&txt-size=16&txt-align=left,top&mark64=${base64url(
`${author.image.url}?w=30&h=30`,
)}&mark-x=${20}&mark-y=${46}&blend64=${base64url(
`https://assets.imgix.net/~text?txtsize=24&txt-color=009688&w=${
width - 80
}&txt-align=middle&txtfont=Hiragino%20Sans%20W6&txt-track=2&txt64=${base64url(
title,
)}`,
)}&blend-mode=normal&blend-align=top,left&blend-x=40&blend-y=100`}
media="(min-width: 1400px)"
type="image/webp"
/>
<source
srcSet={`${baseImageUrl}?w=${width * 0.8}&h=${
height * 0.8
}}&txt64=${base64url(
author.name,
)}&txt-pad=40&txt-color=00695C&txt-size=12&txt-align=left,top&mark64=${base64url(
`${author.image.url}?w=24&h=24`,
)}&mark-x=${16}&mark-y=${32}&blend64=${base64url(
`https://assets.imgix.net/~text?txtsize=20&txt-color=009688&w=${
width - 180
}&txt-align=middle&txtfont=Hiragino%20Sans%20W6&txt-track=2&txt64=${base64url(
title,
)}`,
)}&blend-mode=normal&blend-align=top,left&blend-x=40&blend-y=90`}
media="(min-width: 1280px)"
type="image/webp"
/>
<source
srcSet={`${baseImageUrl}?w=${width * 0.6}&h=${
height * 0.6
}}&txt64=${base64url(
author.name,
)}&txt-pad=40&txt-color=00695C&txt-size=12&txt-align=left,top&mark64=${base64url(
`${author.image.url}?w=24&h=24`,
)}&mark-x=${16}&mark-y=${32}&blend64=${base64url(
`https://assets.imgix.net/~text?txtsize=16&txt-color=009688&w=${
width - 240
}&txt-align=middle&txtfont=Hiragino%20Sans%20W6&txt-track=2&txt64=${base64url(
title,
)}`,
)}&blend-mode=normal&blend-align=top,left&blend-x=10&blend-y=70`}
media="(min-width: 960px)"
type="image/webp"
/>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={`${baseImageUrl}?w=${width * 0.55}&h=${
height * 0.55
}}&txt64=${base64url(
author.name,
)}&txt-pad=40&txt-color=00695C&txt-size=12&txt-align=left,top&mark64=${base64url(
`${author.image.url}?w=24&h=24`,
)}&mark-x=${16}&mark-y=${32}&blend64=${base64url(
`https://assets.imgix.net/~text?txtsize=12&txt-color=009688&w=${
width - 280
}&txt-align=middle&txtfont=Hiragino%20Sans%20W6&txt-track=2&txt64=${base64url(
title,
)}`,
)}&blend-mode=normal&blend-align=top,left&blend-x=10&blend-y=70`}
alt={title}
/>
</picture>
);
};
動的OGP画像を生成する
imgixAPIを用いて動的にOGP画像を生成する方法がイメージいただけたでしょうか?
これを転用してOGP画像を作成します。OGP画像とはTwitterなどでページを共有させる際に出るサムネイルです。
これを設定することで、インプレッションの向上が期待できます。
先ほどの画像生成はコンポーネントを返すのに対して、OGP画像を設定するためには動的画像のURLを返してあげる必要があります。
例えば、Next.jsではnext/head
にURLを含めることでOGP画像のURLを設定することができます。実際のユースケースは以下のようになります。
import Head from 'next/head';
export type HeadType = {
pagetitle?: string;
pagedescription?: string;
pagepath?: string;
pageimg?: string;
postimg?: string;
pageimgw?: string;
pageimgh?: string;
keyword?: string;
};
export const HeadTemplate: React.FC<HeadType> = ({
pagetitle,
pagedescription,
pagepath,
pageimg,
postimg,
pageimgw,
pageimgh,
keyword,
}) => {
const title = pagetitle
? `${pagetitle} | ${process.env.NEXT_PUBLIC_TITLE}`
: `${process.env.NEXT_PUBLIC_TITLE}`;
const description =
pagedescription || `${process.env.NEXT_PUBLIC_DESCRIPTION}`;
const url = pagepath
? `${process.env.NEXT_PUBLIC_URL}${pagepath}`
: `${process.env.NEXT_PUBLIC_URL}`;
// 画像のURLは以下のようにして、画像の指定がない時にはデフォルトの画像をおいて設定されるようにすると良いです。Next.jsであれば public/配下に画像を設置することができます。
// NEXT_PUBLIC_URLは環境変数です。https://・・・・のようなURLをホスティングサービス側で設定します
const imgurl = pageimg
? `${process.env.NEXT_PUBLIC_URL}${pageimg}`
: postimg || `${process.env.NEXT_PUBLIC_URL}banner.png`;
const imgw = pageimgw || '1200px';
const imgh = pageimgh || '630px';
const fbAppId = `${process.env.FB_APP_ID}`;
return (
<Head>
<html lang="ja" />
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta property="og:description" content={description} />
<meta property="og:site_name" content={title} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta name="keywords" content={keyword || title} />
<meta property="og:url" content={url} />
<meta property="og:type" content="website" />
<meta property="og:locale" content="ja_JP" />
<meta property="fb:app_id" content={fbAppId} />
// 動的画像のURLをこのように指定することによって、OGPに適用されます。
<meta property="og:image" content={imgurl} />
<meta property="og:image:width" content={imgw} />
<meta property="og:image:height" content={imgh} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:url" content={imgurl} />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={imgurl} />
<link rel="canonical" href={url} />
</Head>
);
};
このURLに渡すURLを生成する関数を作成します。
import base64url from 'base64url';
type Author = {
id: string;
name: string;
image: {
url: string;
};
};
export const createOgImage = (
baseImageUrl: string,
author: Author,
title: string,
) => {
const ogImageUrl = `${baseImageUrl}?w=1200&h=1200&txt64=${base64url(
author.name,
)}&txt-pad=90&txt-color=00695C&txt-size=24&txt-align=left,top&mark64=${base64url(
`${author.image.url}?w=40&h=40`,
)}&mark-x=40&mark-y=80&blend64=${base64url(
`https://assets.imgix.net/~text?txtsize=48&txt-color=009688&w=1120&txt-align=middle&txtfont=Hiragino%20Sans%20W6&txt-track=2&txt64=${base64url(
title,
)}`,
)}&blend-mode=normal&blend-align=top,left&blend-x=80&blend-y=180`;
return { ogImageUrl };
};
基本的なコードは同じです。URLの文字列を返しているところがポイントです。
こちらの関数を使用して、画像URLを渡してあげましょう。Next.jsの場合はpages/blogs/[id].tsx
のような動的に生成されるページで使用します。
const BlogDetail: React.FC<Props> = ({ blog }) => {
const { ogImageUrl } = createOgImage(
blog?.image?.url,
blog?.author?.find((author) => author),
blog.title,
);
return (
<Layout>
<HeadTemplate
pagetitle={blog.title}
pagedescription={blog.title}
pagepath="blogs"
postimg={ogImageUrl}
/>
// 省略
);
};
export default BlogDetail;
これで動的なOGP画像が生成されるようになるかと思います。ぜひ、お試しください!
Tips
【表示チェックツール】
Twitterカードが正しく表示されているかをチェックするのに便利な公式ツールです。またOGP画像を生成する上で必要な値は公式ドキュメントでご確認ください。
https://cards-dev.twitter.com/validator
【OGP画像チェックツール】
imgixで長いパラメータを付与した場合にOGP画像が表示されないケースがあります。その際は短いURLから徐々に試してみましょう。ローカルのOGPをチェックするには以下のGoogle Chromeの拡張機能が便利です。
https://chrome.google.com/webstore/detail/localhost-open-graph-chec/gcbnmkhkglonipggglncobhklaegphgn?hl=ja
終わりに
imgixをmicroCMS経由で利用すると様々な画像加工ができてとても便利です。今回のケースは一例にすぎません。ぜひ、色々なパラメーターを試してみてください!
参考記事
pentaprogram.tokyo「OG画像だって自動生成できる!そう、microCMSならね」
ライブラリ選定や関数でURLを返す実装、日本語の設定など参考にさせていただきました。ありがとうございます。
-----
microCMSは日々改善を進めています。
ご意見・ご要望は管理画面右下のチャット、公式Twitter、メールからお気軽にご連絡ください!
引き続きmicroCMSをよろしくお願いいたします!