Webサービスで画像生成サービスを利用して生産性をあげよう

概要

Webサービスやスマートフォアンプリで使う写真のような画像は表示する解像度やRetina対応のため複数用意する必要があります。この問題に対応するためのいくつか方法を紹介します。その中の画像生成サービスを利用する方法について具体的なサービスを紹介し、imgixを利用したサンプルを紹介します。

目的

Webサービスやスマートフォンアプリで使う写真のような複雑な画像は表示する解像度やRetina対応のため複数用意する必要があります。
Webサービスにおいてこの問題を解決する方法をいくつか提示・比較します。比較した中でできるだけ作業工数の少ない方法を提案しサンプルコードを共有します。

対象読者

Webサービスの設計を担当するソフトウェアエンジニア

ソフトウェアエンジニアに複数解像度を手作業で作成させられそうになっているデザイナ


本文


Webサービスやスマートフォンアプリで使う写真のような細かい画像は、表示する解像度やRetina対応のため複数用意する必要があります。この問題に対応するために以下のような方法があります。

  1. PC用の高解像度の画像をモバイル用の小さい領域に当てはめる
  2. 手動で複数の解像度を用意する
  3. ビルド時に複数の解像度の画像を生成する
  4. 画像生成サービスを利用する

本記事後半では画像生成サービスについていくつか具体なサービスを紹介し、その中でimgixを利用したサンプルを紹介します。

※アイコンやシンプルなイラストはSVG形式でベクター画像にしておくと複数画像を用意する必要がないのでおすすめです。

各方法のメリット・デメリット

  1. PC用の高解像度の画像をモバイル用の小さい領域に当てはめる

    • メリット
      • 画像作成時に何も考えなくて良い
      • 高解像度が必要なディスプレイでも画像が滲まない
    • デメリット
      • 低解像度やモバイルでも画像が表示される
      • 特にモバイルの場合通信量用を食うので、LTE回線だと表示が遅くなる。いわゆるギガを無駄に消費させてしまう
  2. 手動で複数の解像度を用意する

    • メリット
      • 解像度にあった適切な画像を表示してくれる
      • 3,4のような自動画像の生成の設定をしなくてて良い
    • デメリット
      • FigmaやXDで画像の生成がとても手間
        • 特にレスポンシブ対応する場合、複数の画面幅に対応する必要がある
      • ImgタグやPictureタグ内のsrcsetコードも手作業で書く必要がある。ある程度自動生成しても確認作業が大変
  3. ビルド時に複数の解像度の画像を生成する

    • メリット
    • デメリット
      • Webpack等の設定がやや面倒
      • 静的サイトの場合、画像が増えるとビルドに時間がかかる
      • CDNの設定を自前でやる必要がある
  4. 画像生成サービスを利用する

    • メリット
      • 解像度にあった適切な画像を表示してくれる
      • ビルド時に画像を生成せず、動的に画像を生成してくれる
      • CDNの設定を自前でやる必要がない
    • デメリット
      • 画像
        • 生成サービス分料金がかかる ※それでも人件費を考えたら十分安い

メリット・デメリットをまとめ

Name 1. PC用の高解像度の画像をモバイル用の小さい領域に当てはめる 2. 手動で複数の解像度を用意する 3. ビルド時に複数の解像度の画像を生成する 4. 画像生成サービスを利用する
解像度にあった適切な画像を表示してくれる ❌ ⭕ ⭕ ⭕
デザイナの作業コストが低い ⭕ ❌ ⭕ ⭕
フロントエンジニアの作業コストが低い ❌ ❌ ❌ ⭕
面倒な設定が不要 ⭕ ⭕ ❌ ⭕

以上から

4**. 画像生成サービスを利用する**が開発工数を圧縮するためには有効だと思われます。

個人サービスや画像の量が少ないのであれば 3. ビルド時に複数の解像度の画像を生成する も悪くない選択肢だとは思います。
それでも4.画像生成サービスを利用するもimgixやCloudinalyの無料枠で十分使えるので画像生成サービスを使うのが生産性高いだろうという見解です。

画像生成サービスの紹介

画像生成サービスは下記のようなサービスがあり、だいたいできることは同じです。

  • imgix

    • 転送量に対して課金されない
    • ファイル数に対してプランごとに制限がある
    • S3などファイルストレージは自前で用意する必要がある
    • 画像数が膨大でないかつアクセスが多いサイトはお得
    • imgixプランのリンク: https://imgix.com/pricing
  • Cloudinary

    • ファイル数に対して課金されない
    • ストレージサイズによって制限がある(無料で25GB)
    • AWSのS3などファイルストレージを自前で保つ必要がない
    • Cloudinaryプランのリンク: https://cloudinary.com/pricing
  • imageflux

    • 国内クラウドサービスベンダーのさくらインターネット社製のサービス
    • 国内の大きめのスタートアップ利用されている
    • 価格が明示されていないのでエンタープライズ向けの価格設定だと思われる
    • 大量の画像を使う場合はボリュームディスカウントが効きそう
    • 国産のサービスなのでこのシステムを使ってのトラブルや技術的なサポートは受けやすいだろう
    • imagefluxの紹介リンク: https://www.sakura.ad.jp/services/imageflux/

imgixを利用した具体的な実装方法

ここからは具体的にAWSのS3に設置した画像をimgixが参照する設定方法を説明します。

事前準備としてimgixに新規アカウントを作成してください。

imgixにアクセスすると、まだ何も作成していない場合は下記のような画面が表示されるので ADD A SOURCE ボタンを押して新しいimgixのソース(imgixの作成するサービスの単位)を作成してください。

新規にimgix.netのサブドメイン(今回は kon-yu )を作成し、画像の取得元にS3を選択します。

imgixがアクセスするS3のバケット(今回のサンプルでは imgix-blog-test-bucket )を作成し、そのS3のバケットにImgixからアクセスできるように、IAMユーザを作成します。

作成したIAMユーザに必要な下記のIAMポリシーを作成し、IAMユーザにアタッチします。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation"],
      "Resource": [
        "arn:aws:s3:::YOUR_BUCKET_NAME",
        "arn:aws:s3:::YOUR_BUCKET_NAME/*"
      ]
    }
  ]
}

作成したIAMユーザのアクセスキーとシークレットキーをimgixの管理画面に登録し、デプロイボタンを押します。

しばらくするとStatusがDeployedになるので準備完了です。

imgixの動作確認

S3に適当な画像を設置します。今回使用した画像: https://source.unsplash.com/8xB2cH9a5pY

S3のバケットにある画像データに直接アクセスするとエラーになるのが確認できます。こちらにアクセスすると、下記のように権限不足のエラーが返却されます。
https://imgix-blog-test-bucket.s3.ap-northeast-1.amazonaws.com/photo-1620050649633-1f9a8c981346.jpeg

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>94961V8EH4GQGQXA</RequestId>
<HostId>9XzEWailQq/LOIuIFZDzHNneqTGGrGal5WMTNlguFdE6VONV1nRRqChqa+1+fCRq17qOmYMyYGk=</HostId>
</Error>

今度はImgixを通した形で先程の画像にアクセスすると、画像を取得することができます。

https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg

またimgixの仕様で縦横幅のサイズを指定して切り抜いたり、

https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?w=400&h=200&fit=crop

顔認識させて顔にフォーカスして画像を作成することができます。

https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?w=400&fit=facearea&facepad=2

Next.jsに組み込む

Next.jsは組み込みライブラリnext/imageで画像をいい感じにしてくれます。
具体的には解像度ごとsrcsetを生成してくれたり、レスポンシブ対応するために複数異なるサイズの画像をsrcsetに作成してくれます。

srcsetで指定された画像は、ブラウザからリクエストされたタイミングで動的に生成されimgixにキャッシュされます。そのため動的に画像を生成するためビルド時にすべての画像を作成するような手間がありません。

imgixがキャッシュの保持・破棄をするため参照元のS3が生成した余分なファイルを持つこともありませんのでファイルの管理が煩雑にならず、バケットサイズを小さく保つことができます。

設定

next/imageをimgixで利用するためにコンフィグファイルでローダーを設定します。

imgix以外にも現在のところVercel、Cloudinary、Akamai がnext.jsで公式サポートされています。独自ローダーを実装することも可能です(コードを読む限りそんなに難しくはないです)。

next.config.js

module.exports = {
  //Next/imageで利用するローダーをimgixに指定する
  images: {
    loader: 'imgix',
    path: 'https://kon-yu.imgix.net/',
  },
}

具体的な実装

レスポンシブに対応の画像は、layoutをresponsiveにします。

srcのURLパラメタでは、width、heightに合わせるように、画像のアスペクト比を調整しています。そのため ar を指定してアスペクト比を 12:9 (もちろん4:3でも可)で画像を切り取るように設定しています。

import Image from "next/image";

<Image
  src={"/photo-1620050649633-1f9a8c981346.jpeg?ar=12:9&fit=crop"}
  width={1200}
  height={900}
  layout="responsive"
/>;

これをビルドするとこのようなHTMLが出力されます。

next/imageがsrcsetの画像サイズに合わせてimgixのURLを &w=640 や &w=1920 など自動で付与しているのがわかります。

<img
  src="https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=3840"
  decoding="async"
  data-nimg="responsive"
  style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%"
  sizes="100vw"
  srcset="
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=640   640w,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=750   750w,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=828   828w,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=1080 1080w,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=1200 1200w,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=1920 1920w,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=2048 2048w,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=12%3A9&amp;fit=crop&amp;auto=format&amp;w=3840 3840w
  "
/>

固定幅の画像の場合はlayoutをfixedにします。

後の設定はレスポンシブの場合と同じようにアスペクト比を指定します。

import Image from "next/image";

<Image
  src={"/photo-1620050649633-1f9a8c981346.jpeg?ar=1:1&fit=crop"}
  width={200}
  height={200}
  layout="fixed"
/>;

これをビルドするとこのようなHTMLが出力されます。

next/imageがsrcsetの画像解像度1x 2xに合わせてimgixのURLを &w=256 や &w=640 など自動で付与してくれます。

<img
  src="https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=1%3A1&amp;fit=crop&amp;auto=format&amp;w=640"
  decoding="async"
  data-nimg="intrinsic"
  style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%"
  srcset="
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=1%3A1&amp;fit=crop&amp;auto=format&amp;w=256 1x,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?ar=1%3A1&amp;fit=crop&amp;auto=format&amp;w=640 2x
  "
/>

注意

jsxの中で表示する縦横幅がwidth, heightで固定しているので、srcにパラメタを ?w=200&h=200 としてしまうと画像の縦横のサイズが200pxで固定されてしまい、next/imageが自動で処理してくれないので注意が必要です。

import Image from "next/image";

<Image
  src={"/photo-1620050649633-1f9a8c981346.jpeg?w=200&h=200&fit=crop"}
  width={200}
  height={200}
  layout="fixed"
/>;

これをビルドするとこのようなHTMLが出力されてしまいます。

next/imageがsrcsetの画像解像度1x 2xが作られているにも関わらず &w=200 や &h=200 の固定された解像度になってしまいます。つまりRetinaディスプレイ等でぼやけた画像が表示される可能性があります。

<img
  src="https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?w=200&amp;h=200&amp;fit=crop&amp;auto=format"
  decoding="async"
  data-nimg="fixed"
  style="position:absolute;top:0;left:0;bottom:0;right:0;box-sizing:border-box;padding:0;border:none;margin:auto;display:block;width:0;height:0;min-width:100%;max-width:100%;min-height:100%;max-height:100%"
  srcset="
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?w=200&amp;h=200&amp;fit=crop&amp;auto=format 1x,
    https://kon-yu.imgix.net/photo-1620050649633-1f9a8c981346.jpeg?w=200&amp;h=200&amp;fit=crop&amp;auto=format 2x
  "
/>

サンプルコードはこちら

GitHub - konyu/nextjs-imagetag-sample at imgix_samplehttps://github.com/konyu/nextjs-imagetag-sample/tree/imgix_sample

考察・提案

画像を必要なサイズにリサイズしたり、レスポンシブ用に画像を用意するような作業は、デザイナとフロントエンジニアの狭間の作業見過ごされがちです。往々にしてパフォーマンスを気にできる殊勝なソフトウェアエンジニアが対処することになります(チームにフロントエンジニアがいない場合も多いですしね)。

このような作業をいかに誰かのせいにすることなく、しくみで解決するのが、よい開発組織ではないでしょうか?

Next.jsを使う場合、ホスティングをVercelにすることにより、next/imageを利用した画像の最適化もデフォルトの設定で出来るのでかなり工数を削減できます。SPAのサービスを作る場合は選択肢に入れてもいいのではないでしょうか?特に個人開発やサービスの規模が比較的小規模の場合は抜群に相性が良さそうです。

まとめ

Webページやウェブサービスにおいて、画像を解像度ごとに用意する方法を4つ紹介し、それぞれのメリット・デメリットをまとめました。その中から画像生成サービスがどういう物があるのか例を上げ、その中の一つであるimgixの具体的な使い方、next.jsでの組み込み方を紹介しました。

参考リンク

← 記事一覧へ