2022.04.06[Wed]

next/imageでサイズ指定せずにアスペクト比を保って表示する

  • React
  • Next.js

目次

  • - 概要
  • - 実装方法
  • - layout="fill"を指定する
  • - next/imageのスタイルを上書きする

概要

next/imageは画像を自動で最適化してくれる便利なコンポーネントです。
使用には基本的にサイズ(width, height)を事前に指定する必要がありますが、APIから画像を受け取って表示する場合など、事前にサイズを指定したくないケースがあると思います。

この記事では、そのような場合でもnext/imageを使って画像を表示する方法をご紹介します。
環境としては、Next 12.1.2 を使用します。

実装方法

最終的なコードはこちらになります。

// JS

<div className={styles.imageContainer}>
  <Image
    className={styles.image}
    src={path}
    layout="fill"
    objectFit="contain"
  />
</div>

// CSS

.imageContainer {
  >span { // ⭐️ Next12未満では span -> div に変更する必要があります。詳細は後ほど説明します。
    position: unset !important;
  }

  .image {
    position: relative !important;
    width: 100% !important;
    height: unset !important;
  }
}

順を追って見ていきます。

layout="fill"を指定する

next/imageは必ずwidth + heightまたはlayout="fill"を指定する必要があります。
本記事では、サイズ指定しないのでlayout="fill"を指定します。
どちらも指定しなかった場合は、下記のエラーが表示されます。

Error: Image with src "xxx" must use "width" and "height" properties or "layout='fill'" property.

next/imageのスタイルを上書きする

next/imageで生成される要素は、「<img>」+「<img>をラップする要素」となっています。
「<img>をラップする要素」は、Nextのバージョンで違いがあります。

  • Next 12未満:<div>タグでラップされる
  • Next 12以降:<span>タグでラップされる

実際に生成される要素はこちらです。

// <Image src={path} layout="fill" objectFit="contain" />
// こちらを指定した際に生成される要素

<span
  style="
    box-sizing:border-box;
    display:block;
    overflow:hidden;
    width:initial;
    height:initial;
    background:none;
    opacity:1;
    border:0;
    margin:0;
    padding:0;
    position:absolute;
    top:0;
    left:0;
    bottom:0;
    right:0
  "
>
  <img
    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%;
      object-fit:contain
    "
    ...
  />
</span>

<span><img>position:absolute;が設定されているので、そのままのスタイルだと画像が表示されません。そのため、表示されるようにスタイルを上書きしていきます。

1. <span>のスタイルを上書きする

.imageContainer {
  >span { // ⭐️ Next12未満では span -> div に変更する必要があります。
    position: unset !important;
  }

<span>にスタイルを当てるためには、next/imageをdivなどでラップする必要があります。
これはnext/imageにクラス名を指定すると<img>にクラス名が付与されてしまい、<span>に直接スタイルを当てるすべがないためです。
ラップした要素から<span>に対して、position:absolute;position: unset !important;で上書きします。

2. <img>のスタイルを上書きする

.image {
  position: relative !important;
  width: 100% !important;
  height: unset !important;
}

次に、<img>のスタイルを上書きします。
こちらは以下の3点を上書きします。

  1. position:absolute;position: relative !important;
  2. width: 0;width: 100% !important;
  3. height: 0;height: unset !important;

これでアスペクト比を保ちつつ画像を表示させることができます。

参考:
https://github.com/vercel/next.js/discussions/18739

Share

Shopifyアプリの開発フローと準備大量のTypeScriptの型エラーを解決した話