Next.jsのImageが覚えられないよ〜

はじめまして、株式会社エイチームウェルネスの@ayanerv_2045です。

名前に「2045」が入っているのは、エンジニアリングの勉強を始めた当時毎日退屈していて、はやく[シンギュラリティ](https://ja.wikipedia.org/wiki/%E6%8A%80%E8%A1%93%E7%9A%84%E7%89%B9%E7%95%B0%E7%82%B9)が来てほしかったからです。

普段はデザイナーとして主にグラフィックの制作をしていますが、たまに簡単なフロントエンドの業務も担当するので、今回アドベントカレンダーに参加させていただきました。
実は初めてのQiita執筆です。お手柔らかにお願いします:writing_hand:

## はじめに
この記事では、Next.jsでのフロント開発で登場する```Image```コンポーネントの便利な使い方について説明します。

- たまにしかフロントを触らないせいで**永遠にImageの使い方が覚えられない**
- 見よう見まねでImageを使ってみるが、**エラー祭りで気が滅入ってしまう**
- **画像サイズのレスポンシブな調整**をjsにやってもらいたい
- HTMLは書けるけど、**HTMLと違うNext.js特有のあれこれにつまずきがち**

という私のような方におすすめです。

この記事を読んでImageコンポーネントの使い方を覚えると、このように高さも幅もいい感じに可変する、綺麗なレスポンシブデザインが実現できます！

![ezgif-3-133abf2a4f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/d6f0623e-a869-393a-a5a3-51f7054b8d41.gif)


## Next.jsのImageって？
Next.jsでは画像を表示するとき、HTMLの```img```タグよりも**Next.jsが用意した```Image```コンポーネント**を使用することが推奨されています。

そもそも```Image```コンポーネントとは、Next.jsのビルトインコンポーネント（簡単な記述で最初から使えるようにNext.jsさんが用意してくれているもの）です。

▼より詳しい説明はこちらで（日本語翻訳版です）

https://nextjs-ja-translation-docs.vercel.app/docs/api-reference/next/image

　
次のコードのように、Imageを使いたいtsxファイルの先頭で```import```しておけば、ページ内のどこでも```<img>```タグの代わりにImageを使えるようになります。

~~~ react:pages.tsx
import Image from 'next/image';

{/* 中略 */}

<Image
  src='/santa.jpg'
  alt='クリスマスの写真'
  width={400}
  height={400}
/>
~~~


altやwidth・heightを持たせるところはHTMLと同じです。

srcは、**publicフォルダ配下**を参照しますので、画像は基本的にpublicに置きましょう。public配下にフォルダを作って細かく画像を管理することも可能です。
上のコードでは、
~~~ react:pages.tsx
src='/santa.jpg'
~~~
と書くことで、```public/santa.jpg```を参照しています。


:::note warn
**ワンポイントアドバイス**
Imageコンポーネントは、ほとんどの場合でwidth・heightの指定を要求してきます（エラー吐きます）。
widthとheightの数値は、表示したい画像の画面上での大きさ、あるいはその画像の元の大きさを記述しておいてください。

width・heightの指定がいらないパターンは後ほど説明します。
:::

## layoutを設定して、画像をレスポンシブ対応しよう
ここからが本題です。

Next.jsのImageコンポーネントは、「いい感じに画像のサイズを調整してくれる」ように作られています。
例えば、画像を囲むDOM要素の幅が小さくなるにつれて、比率を保ちながら画像の幅もそれに合わせて調整してくれたり。

しかし、画像が思い通りにサイズ調整されるようなスタイリングをするには、いくつかのプロパティの使い方を覚えて、正しく使う必要があります。
```layout```、```objectFit```、```objectPosition```です。

layoutから順に説明します。

## layout
ビューポート（画像を表示する範囲）のサイズが変わった時、画像がどのような挙動をするかを設定するプロパティです。
正しく記述すれば、このような挙動が実現できます。
（記事冒頭でお見せしたものと同じです）

![ezgif-3-133abf2a4f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/d6f0623e-a869-393a-a5a3-51f7054b8d41.gif)

上手く使えば綺麗なレスポンシブデザインを実装できそうだなぁというのが伝わるでしょうか？
ビューポート（画像を表示する範囲）のサイズが小さくなっても、アスペクト比を崩すことなく画像の表示範囲が可変し、しかも右側のテキストの高さと揃っています。
CSSで書くと地味にややこしいこういった挙動が、プロパティの使い方さえ覚えれば2,3行で書けます。

では、```layout```プロパティにはどのような値があるのか紹介していきます。

### intrinsic
デフォルトの値です。layoutプロパティを何も記述しないとintrinsicになります。
元の画像サイズの横幅を上限に、アスペクト比を保って拡大縮小します。
シンプルに画像を表示するだけならこれでも良いのですが、下のGIFのようにテキストなど他の要素と並ぶとガタガタしますね。

![ezgif-3-ba5b885f11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/35d47fb7-d25f-09a9-af71-313855b19a71.gif)

### fixed
読んで字のごとく、widthとheightが固定になります。

![ezgif-3-38f801b44e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/434f0f81-6d7a-5ac5-88a5-9271b5212062.gif)

### responsive
出ました、responsiveです。
筆者は最初Imageを使った時この辺りから「覚えられないよ〜」となっていました。

responsiveにすると、表示範囲の**横幅**に合わせて拡大縮小します。

![ezgif-3-3b71ae5d0c.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/df733393-1474-497a-bf9c-7b31bd360a75.gif)

これだけみると別に普通に拡大縮小しているだけなのですが、次に出てくる```fill```や、```fill```とペアで登場するプロパティの```objectFit```を使った挙動と混同していたのです…。

私のように混乱しないためには、「**responsiveは横幅可変**」と覚えると良いです。
PC〜タブレット〜SPへと**画面幅**が変わっても、滑らかに要素のサイズが変わって崩れないデザインのことを「**レスポンシブデザイン**」って言いますよね。
よく考えるとあれは画面の高さではなく**幅に応じて変化する**ものなので、「レスポンシブということは横幅可変か〜」と思うと覚えやすいかもしれません。

ちなみに私は「レスポンシブ＝なんかいい感じに空気読んで可変してくれるもの」と勘違いしていて、「あれっ高さは親要素に合わせてくれないの？」と混乱してました。

なお、responsiveは親要素が```display: block```でないと上手く動きませんので気をつけてください。

### fill
高さと幅の両方を親要素いっぱいにして、画像の表示範囲を埋めます。
CSSの```background-size: cover```みたいな挙動です。

:::note warn
fillにすると、画像が画面いっぱいに広がってしまうことがあります。
その場合は、親要素を```position: relative;```とし、widthとheightを持たせるようにしてください。
:::

　
先程の```layout={"responsive"}```の場合、横幅は可変しても高さは隣のテキストと揃わずガタガタしていました。
しかしfillは高さと幅両方に合わせて広がるので、隣のテキストとも高さが揃い、綺麗なレスポンシブデザインを実現できます。

![ezgif-3-9da563d3b3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/b54a10a6-e4d4-bd43-1b83-3cfae0bd6d94.gif)

…と思いきや、fillはこれだけではアスペクト比を無視してしまい、上のようになります。
ここで登場するのが、```layout```に続くもう一つのプロパティ、```objectFit```です。

## objectFit
objectFitは、**CSSにおけるobject-fitと使い方も挙動も同じ**です。

例えば、```objectFit={"cover"}```というようにImageコンポーネントに記述します。
この時、Imageコンポーネントがクライアント側でimgタグに変換されたものに当たっているスタイルを見ると、```object-fit: cover```が当たっているのが分かります。

![スクリーンショット 2022-12-13 12.14.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/9e97b42b-6881-2b00-d162-2731e74b9867.png)


▼CSSにおけるobject-fitについてはこちら

https://developer.mozilla.org/ja/docs/Web/CSS/object-fit

　
先ほど```layout={"fill"}```と記述したときにアスペクト比を無視して拡大縮小していたのは、このobjectFitを記述することで解決できます。

- fill
- cover
- contain

のいずれかを指定して、fillの挙動を決められます。

### cover

![ezgif-3-ceb6a85705.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/18073380-d578-b4d6-1b16-c2861ac67ec5.gif)

**cover**の場合は、画像の表示範囲全体を覆うように画像が拡大縮小します。
CSSの```background-size: cover```みたいな挙動です。
表示範囲全体を覆う代わりに、画像の一部が見えなくなってしまうため、画像が切れると困る場合は、

- 被写体を写真の中央に配置して、切れないようにする
- ```objectPosition```プロパティをImageコンポーネントに渡して、親要素内にどう配置されるかを定義しておく

のどちらかで対処しましょう。
```objectPosition```プロパティの記述はCSSと同じですので、今回は割愛させていただきます。

https://developer.mozilla.org/ja/docs/Web/CSS/object-position


### contain

![ezgif-3-bd3e7e9089.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/9dc34caa-aff9-8a65-0ba6-a1f9ef6932c6.gif)

**contain**の場合は、画像全体が表示範囲に入るように調整されます。
CSSの```background-size: contain```みたいな挙動です。
coverと違って画像が切れることはありませんが、画像の表示範囲に余白ができることがあります。
（ちょっと上のGIFだとわかりにくいですね…すいません）

# これらのプロパティを組み合わせると
冒頭でご紹介した、「**ビューポート（画像を表示する範囲）のサイズが小さくなっても、アスペクト比を崩すことなく画像の表示範囲が可変し、しかも右側のテキストの高さと揃う**」デザインを実装できます。

```layout={"fill"}```で「画像を高さ・幅に合わせて拡大縮小」よう設定しつつ、```objectFit={"cover"}```で「アスペクト比を保って表示範囲いっぱいに画像を広げる」ように指定しています。

![ezgif-3-133abf2a4f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/337646/d6f0623e-a869-393a-a5a3-51f7054b8d41.gif)

~~~ react:pages.tsx
<div className={styles.santa_wrap}>
  <p>▼これを目指します</p>
  <div className={styles.santa_flex}>
    <div className={styles.santa_image}>
      <Image
        src='/santa.jpg'
        alt='クリスマスの写真'
        layout={"fill"}
        objectFit={"cover"}
      />
    </div>
    <p className={styles.santa_text}>
      メリークリスマス！今年もアドベントカレンダーの季節がやってきたね！<br />
      メリークリスティアーノロナウド！今年もアド弁当カレンダーの季節がやってきたね！<br />
      メリークリス松村！！今年もアドベントカレーの季節がやってきたね！<br />
      メリークリスマス！今年もアドベンチャー滝沢カレンの季節がやってきたね！<br /><br />
      クリスマスとは、イエス・キリストの降誕祭です。クリスマスとは、イエス・キリストの降誕祭です。クリスマスとは、イエス・キリストの降誕祭です。<br/>
      すごいですね。
    </p>
  </div>
</div>
~~~

~~~ css:santa.css
.santa_wrap {
  max-width: 70%;
}
.santa_flex {
  display: flex;
  justify-content: space-between;
}
.santa_image {
  flex-basis: 48%;
  position: relative;
  margin-right: 20px;
  display: block;
}
.santa_text {
  flex-basis: 48%;
  margin: 0;
}
~~~

# 最後に
ここまで、Next.jsのImageコンポーネントについて説明しました。
Imageコンポーネントは、よくよく考えるとCSSと同じような部分が多く、混乱することもないかもしれないのですが、今までHTMLをメインに書いていたデザイナーや初見の人はプロパティの書き方がわからず身構えると思います。
そういった方向けにImageコンポーネントのプロパティについて動画付きで説明している記事はあまり見かけないので、お役に立てれば嬉しいです。