🏙️

Nuxt 3の画像の動的表示でつまづいたところ

2024/12/07に公開

本記事は、mediba Advent Calender2024の7日目の記事になります。

問題

以下のような要件で画像を動的に表示しようとしていました。

  • JSONから取得した画像のURLを指定する
  • 画像のURLが空or存在しない場合は、デフォルト画像を表示する
  • 画像の取得に失敗した場合はエラー画像を表示する

しかしimgタグのsrcをv-bindで動的に設定したところ、画像が表示されなくて困りました。
解決までの備忘を残します。

過程

まず、静的srcでは問題なく表示されました。

<template>
 <img src="@/assets/default.png" />
</template>

これをv-bindを使って動的表示にしたところ、画像が表示されなくなりました。

<template>
 <img :src="getImageUrl(data)" />
</template>

<script>
const defaultImageUrl = "@/assets/default.png";
function getImageUrl(data) {
  return data.image_url && data.image_url.trim() !== ""
    ? data.image_url
    : defaultImageUrl;
}
</script>

ブラウザで確認すると、静的srcでは/_nuxt/assets/default.pngに変換されていたものが、@/assets/default.pngの文字列のままになっていました。

Nuxtで画像を扱う際、src要素を静的に指定した場合はWebpackによりモジュールとして読み込んでくれる一方で、src属性にv-bindを使い動的に指定するとそのままのパスとして扱われるようです。

また、Nuxt2では以下のようにrequire()を使うことで動的表示が可能でしたが、Nuxt3で使われているViteではrequireをサポートしていないため、この方法は使えません。

<img :src="require(hogehoge)">

解決

方法1:importを使用

今回はデフォルト画像とエラー画像を明示的にインポートすることで解決しました。

import defaultImageUrl from "@/assets/default.png";
import errorImageUrl from "@/assets/failed.png";

画像の数が多い場合はインポートが大変なのでまた検討が必要そうです。

方法2:画像をpublicに配置

publicフォルダに画像を配置すると、ビルド時にパスがそのまま保持されるため、画像を動的に表示することができます。
しかし、publicに置くと画像が最適化されないため、サイズやパフォーマンスの管理が必要です。

余談

今回の要件に必要なエラー画像の表示を@errorを使って実装しようとしたところ、再レンダリングがうまくいかなかったので、最終的にsrcを直接書き換えるのではなく、画像のURLを管理するためのリアクティブなプロパティ(displayImageUrl)を利用することにしました。

<img :src="data.displayImageUrl" @error="onImageError(data)" />
<script>
// ~~ importなど ~~
const fetchData = async () => {
    // ~~ データ取得 ~~
    data.value = data.list
      .map((list) => ({
        ...list,
        content: list.content.map((data) => ({
          ...data,
          // リアクティブなプロパティを追加
          displayImageUrl:
            data.image_url && data_url.trim() !== "" ? data.image_url : defaultImageUrl,
        })),
      }))
};
// エラー画像の表示
const onImageError = (data) => {
  data.displayImageUrl = errorImageUrl;
};
</script>

imageの実装で思わぬ落とし穴にハマりました。
同じ事象でお困りの方の参考になれば幸いです。


最後に、現在medibaではメンバーを大募集しています。
募集・応募ページ
medibaってどんな会社だろう? と興味を持っていただいた方は、
カジュアル面談もやっておりますので、お気軽にお申込み頂ければと思います。

Discussion