Closed2

next/imageで縦横比が不明の画像を扱う

roibanroiban

next/imageのImageコンポーネントでは原則widthheightの指定が必須。縦横比があらかじめ分かっている場合はこれでOK。

https://nextjs.org/docs/api-reference/next/image

とはいえ縦横比が事前に分からない画像を扱う必要もあり、そういう場合にwidhtheightを決めてしまうと本来と異なる縦横比で表示されてしまう。一つの解決策は、

  • layout='fill'objectFit='contain'オプションを指定してwidthheightは決めない。
  • コンポーネントをdivで囲んでこのdivposition: relativewidth(またはheight)のスタイルを適用。

というもの。

https://stackoverflow.com/questions/66353475/how-to-use-image-component-in-next-js-with-unknown-width-and-height
https://github.com/vercel/next.js/discussions/18739

気になるのは、

  • widthheightを指定した場合と比較したパフォーマンス面での差異
  • Imageがうまく回避しているというCLS(Cumulative Layout Shift)対策はどうなっているか
roibanroiban

放置してしまった。この間にNext.js 11がリリースされ、ローカルに保持している画像の widthheight の自動検出機能が追加された。

https://nextjs.org/blog/next-11#automatic-size-detection-local-images

さて、しばらく先述の方法を使ってみたところ、特にパフォーマンス面での問題の原因になるということはなかった。スタイル上の指示に過ぎないので当然ともいえる? 「widthheightの指定を構造的に強制する」というnext/imageの設計は優秀で、画像が原因のCLSはこの方法でも完全に排除されていた。

実践的には毎回書くのは億劫なのでもっぱらコンポーネント化したものを使うことになる。ついでに何らかの理由で画像が読み込めないとき alt のテキストを出すよりは NoImage 的なデフォルト画像を表示したいので、onError で state を切り替えることにして、だいたいこんなふうなコンポーネントを定義する:

import { useState } from "react";
import Image from "next/image";

const ImageWithUnknownDimensions = (props: {
    src: string;
    alt: string;
    className: string;
}) => {
    const [state, setState] = useState<{ src: string }>({ src: props.src });
    return (
        <div className={props.className}>
            <Image
                src={state.src}
                alt={props.alt}
                layout="fill"
                objectFit="contain"
                onError={() => setState({ src: "/noimage.png" })}
            />
        </div>
    );
};

export default ImageWithUnknownDimensions;

注意として、state を使っている都合かページングしている場合に遷移で中身が切り替わらないことがあるので、その場合は key を付与するなりなんなりして更新を強制する。

比率の問題はこれでほぼ対処できたので、このスクラップはclose。

このスクラップは2021/08/03にクローズされました